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  import static com.puppycrawl.tools.checkstyle.AbstractPathTestSupport.addEndOfLine;
24  import static com.puppycrawl.tools.checkstyle.internal.utils.TestUtil.isUtilsClassHasPrivateConstructor;
25  import static org.junit.jupiter.api.Assumptions.assumeTrue;
26  import static org.mockito.Mockito.mock;
27  import static org.mockito.Mockito.mockStatic;
28  import static org.mockito.Mockito.verify;
29  
30  import java.io.BufferedReader;
31  import java.io.ByteArrayOutputStream;
32  import java.io.File;
33  import java.io.IOException;
34  import java.io.PrintStream;
35  import java.io.Serial;
36  import java.nio.charset.StandardCharsets;
37  import java.nio.file.Files;
38  import java.nio.file.Path;
39  import java.util.ArrayList;
40  import java.util.List;
41  import java.util.Locale;
42  import java.util.logging.Handler;
43  import java.util.logging.Level;
44  import java.util.logging.Logger;
45  import java.util.regex.Pattern;
46  import java.util.stream.Collectors;
47  
48  import org.itsallcode.io.Capturable;
49  import org.itsallcode.junit.sysextensions.SystemErrGuard;
50  import org.itsallcode.junit.sysextensions.SystemErrGuard.SysErr;
51  import org.itsallcode.junit.sysextensions.SystemOutGuard;
52  import org.itsallcode.junit.sysextensions.SystemOutGuard.SysOut;
53  import org.junit.jupiter.api.BeforeEach;
54  import org.junit.jupiter.api.Test;
55  import org.junit.jupiter.api.extension.ExtendWith;
56  import org.junit.jupiter.api.io.TempDir;
57  import org.mockito.MockedStatic;
58  import org.mockito.Mockito;
59  
60  import com.puppycrawl.tools.checkstyle.AbstractAutomaticBean.OutputStreamOptions;
61  import com.puppycrawl.tools.checkstyle.api.AuditListener;
62  import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
63  import com.puppycrawl.tools.checkstyle.api.Violation;
64  import com.puppycrawl.tools.checkstyle.internal.testmodules.TestRootModuleChecker;
65  import com.puppycrawl.tools.checkstyle.internal.utils.TestUtil;
66  import com.puppycrawl.tools.checkstyle.utils.ChainedPropertyUtil;
67  
68  @ExtendWith({SystemErrGuard.class, SystemOutGuard.class})
69  public class MainTest {
70  
71      private static final String SHORT_USAGE = String.format(Locale.ROOT,
72              "Usage: checkstyle [OPTIONS]... file(s) or folder(s) ...%n"
73              + "Try 'checkstyle --help' for more information.%n");
74  
75      private static final String USAGE = String.format(Locale.ROOT,
76            "Usage: checkstyle [-dEgGhjJtTV] [-b=<xpath>] [-c=<configurationFile>] "
77                    + "[-f=<format>]%n"
78                    + "                  [-o=<outputPath>] [-p=<propertiesFile>] "
79                    + "[-s=<suppressionLineColumnNumber>]%n"
80                    + "                  [-w=<tabWidth>] [-e=<exclude>]... [-x=<excludeRegex>]... "
81                    + "<files or folders>...%n"
82                    + "Checkstyle verifies that the specified source code files adhere to the"
83                    + " specified rules. By default,%n"
84                    + "violations are reported to standard out in plain format. Checkstyle requires"
85                    + " a configuration XML%n"
86                    + "file that configures the checks to apply.%n"
87                    + "      <files or folders>... One or more source files to verify%n"
88                    + "  -b, --branch-matching-xpath=<xpath>%n"
89                    + "                            Shows Abstract Syntax Tree(AST) branches that"
90                    + " match given XPath query.%n"
91                    + "  -c=<configurationFile>    Specifies the location of the file that defines"
92                    + " the configuration%n"
93                    + "                              modules. The location can either be a"
94                    + " filesystem location, or a name%n"
95                    + "                              passed to the ClassLoader.getResource()"
96                    + " method.%n"
97                    + "  -d, --debug               Prints all debug logging of CheckStyle utility.%n"
98                    + "  -e, --exclude=<exclude>   Directory/file to exclude from CheckStyle. The"
99                    + " path can be the full,%n"
100                   + "                              absolute path, or relative to the current"
101                   + " path. Multiple excludes are%n"
102                   + "                              allowed.%n"
103                   + "  -E, --executeIgnoredModules%n"
104                   + "                            Allows ignored modules to be run.%n"
105                   + "  -f=<format>               Specifies the output format. Valid values: "
106                   + "xml, sarif, plain for%n"
107                   + "                              XMLLogger, SarifLogger, and "
108                   + "DefaultLogger respectively. Defaults to%n"
109                   + "                              plain.%n"
110                   + "  -g, --generate-xpath-suppression%n"
111                   + "                            Generates to output a xpath suppression xml to use"
112                   + " to suppress all%n"
113                   + "                              violations from user's config. Instead of"
114                   + " printing every violation,%n"
115                   + "                              all violations will be catched and single"
116                   + " suppressions xml file will%n"
117                   + "                              be printed out. Used only with -c option. Output"
118                   + " location can be%n"
119                   + "                              specified with -o option.%n"
120                   + "  -G, --generate-checks-and-files-suppression%n"
121                   + "                            Generates to output a suppression xml that will"
122                   + " have suppress elements%n"
123                   + "                              with \"checks\" and \"files\" attributes only to"
124                   + " use to suppress all%n"
125                   + "                              violations from user's config. Instead of"
126                   + " printing every violation,%n"
127                   + "                              all violations will be catched and single"
128                   + " suppressions xml file will%n"
129                   + "                              be printed out. Used only with -c option. Output"
130                   + " location can be%n"
131                   + "                              specified with -o option.%n"
132                   + "  -h, --help                Show this help message and exit.%n"
133                   + "  -j, --javadocTree         This option is used to print the Parse Tree of"
134                   + " the Javadoc comment. The%n"
135                   + "                              file has to contain only Javadoc comment"
136                   + " content excluding '/**' and%n"
137                   + "                              '*/' at the beginning and at the end"
138                   + " respectively. It can only be%n"
139                   + "                              used on a single file and cannot be"
140                   + " combined with other options.%n"
141                   + "  -J, --treeWithJavadoc     This option is used to display the Abstract"
142                   + " Syntax Tree (AST) with%n"
143                   + "                              Javadoc nodes of the specified file. It can"
144                   + " only be used on a single%n"
145                   + "                              file and cannot be combined"
146                   + " with other options.%n"
147                   + "  -o=<outputPath>           Sets the output file. Defaults to stdout.%n"
148                   + "  -p=<propertiesFile>       Sets the property files to load.%n"
149                   + "  -s=<suppressionLineColumnNumber>%n"
150                   + "                            Prints xpath suppressions at the file's line and"
151                   + " column position.%n"
152                   + "                              Argument is the line and column number"
153                   + " (separated by a : ) in the%n"
154                   + "                              file that the suppression should be generated"
155                   + " for. The option cannot%n"
156                   + "                              be used with other options and requires exactly"
157                   + " one file to run on to%n"
158                   + "                              be specified. Note that the generated result"
159                   + " will have few queries,%n"
160                   + "                              joined by pipe(|). Together they will match all"
161                   + " AST nodes on%n"
162                   + "                              specified line and column. You need to choose"
163                   + " only one and recheck%n"
164                   + "                              that it works. Usage of all of them is also ok,"
165                   + " but might result in%n"
166                   + "                              undesirable matching and suppress other"
167                   + " issues.%n"
168                   + "  -t, --tree                This option is used to display the Abstract"
169                   + " Syntax Tree (AST) without%n"
170                   + "                              any comments of the specified file. It can"
171                   + " only be used on a single%n"
172                   + "                              file and cannot be combined with"
173                   + " other options.%n"
174                   + "  -T, --treeWithComments    This option is used to display the Abstract"
175                   + " Syntax Tree (AST) with%n"
176                   + "                              comment nodes excluding Javadoc of the"
177                   + " specified file. It can only be%n"
178                   + "                              used on a single file and cannot be combined"
179                   + " with other options.%n"
180                   + "  -V, --version             Print version information and exit.%n"
181                   + "  -w, --tabWidth=<tabWidth> Sets the length of the tab character. Used only"
182                   + " with -s option. Default%n"
183                   + "                              value is 8.%n"
184                   + "  -x, --exclude-regexp=<excludeRegex>%n"
185                   + "                            Directory/file pattern to exclude from CheckStyle."
186                   + " Multiple excludes%n"
187                   + "                              are allowed.%n");
188 
189     private static final Logger LOG = Logger.getLogger(MainTest.class.getName()).getParent();
190     private static final Handler[] HANDLERS = LOG.getHandlers();
191     private static final Level ORIGINAL_LOG_LEVEL = LOG.getLevel();
192 
193     private static final String EOL = System.lineSeparator();
194 
195     @TempDir
196     public File temporaryFolder;
197 
198     private final LocalizedMessage auditStartMessage = new LocalizedMessage(
199             Definitions.CHECKSTYLE_BUNDLE, getClass(),
200             "DefaultLogger.auditStarted");
201 
202     private final LocalizedMessage auditFinishMessage = new LocalizedMessage(
203             Definitions.CHECKSTYLE_BUNDLE, getClass(),
204             "DefaultLogger.auditFinished");
205 
206     private final String noViolationsOutput = auditStartMessage.getMessage() + EOL
207                     + auditFinishMessage.getMessage() + EOL;
208 
209     private static String getPath(String filename) {
210         return "src/test/resources/com/puppycrawl/tools/checkstyle/main/" + filename;
211     }
212 
213     private static String getNonCompilablePath(String filename) {
214         return "src/test/resources-noncompilable/com/puppycrawl/tools/checkstyle/main/" + filename;
215     }
216 
217     private static String getFilePath(String filename) throws IOException {
218         return new File(getPath(filename)).getCanonicalPath();
219     }
220 
221     /**
222      * Configures the environment for each test.
223      * <ul>
224      * <li>Restore original logging level and HANDLERS to prevent bleeding into other tests;</li>
225      * <li>Turn off colors for picocli to not conflict with tests if they are auto turned on.</li>
226      * <li>Start output capture for {@link System#err} and {@link System#out}</li>
227      * </ul>
228      *
229      * @param systemErr wrapper for {@code System.err}
230      * @param systemOut wrapper for {@code System.out}
231      */
232     @BeforeEach
233     public void setUp(@SysErr Capturable systemErr, @SysOut Capturable systemOut) {
234         systemErr.captureMuted();
235         systemOut.captureMuted();
236 
237         System.setProperty("picocli.ansi", "false");
238 
239         LOG.setLevel(ORIGINAL_LOG_LEVEL);
240 
241         for (Handler handler : LOG.getHandlers()) {
242             boolean found = false;
243 
244             for (Handler savedHandler : HANDLERS) {
245                 if (handler == savedHandler) {
246                     found = true;
247                     break;
248                 }
249             }
250 
251             if (!found) {
252                 LOG.removeHandler(handler);
253             }
254         }
255     }
256 
257     @Test
258     public void testIsProperUtilsClass() throws ReflectiveOperationException {
259         assertWithMessage("Constructor is not private")
260                 .that(isUtilsClassHasPrivateConstructor(Main.class))
261                 .isTrue();
262     }
263 
264     @Test
265     public void testVersionPrint(@SysErr Capturable systemErr, @SysOut Capturable systemOut) {
266         assertMainReturnCode(0, "-V");
267         assertWithMessage("Unexpected output log")
268             .that(systemOut.getCapturedData())
269             .isEqualTo("Checkstyle version: null" + System.lineSeparator());
270         assertWithMessage("Unexpected system error log")
271             .that(systemErr.getCapturedData())
272             .isEqualTo("");
273     }
274 
275     @Test
276     public void testUsageHelpPrint(@SysErr Capturable systemErr, @SysOut Capturable systemOut) {
277         assertMainReturnCode(0, "-h");
278         assertWithMessage("Unexpected output log")
279             .that(systemOut.getCapturedData())
280             .isEqualTo(USAGE);
281         assertWithMessage("Unexpected system error log")
282             .that(systemErr.getCapturedData())
283             .isEqualTo("");
284     }
285 
286     @Test
287     public void testWrongArgument(@SysErr Capturable systemErr, @SysOut Capturable systemOut) {
288         // need to specify a file:
289         // <files> is defined as a required positional param;
290         // picocli verifies required parameters before checking unknown options
291         assertMainReturnCode(-1, "-q", "file");
292         final String usage = "Unknown option: '-q'" + EOL + SHORT_USAGE;
293         assertWithMessage("Unexpected output log")
294             .that(systemOut.getCapturedData())
295             .isEqualTo("");
296         assertWithMessage("Unexpected system error log")
297             .that(systemErr.getCapturedData())
298             .isEqualTo(usage);
299     }
300 
301     @Test
302     public void testWrongArgumentMissingFiles(@SysErr Capturable systemErr,
303             @SysOut Capturable systemOut) {
304         assertMainReturnCode(-1, "-q");
305         // files is defined as a required positional param;
306         // picocli verifies required parameters before checking unknown options
307         final String usage = "Missing required parameter: '<files or folders>'" + EOL + SHORT_USAGE;
308         assertWithMessage("Unexpected output log")
309             .that(systemOut.getCapturedData())
310             .isEqualTo("");
311         assertWithMessage("Unexpected system error log")
312             .that(systemErr.getCapturedData())
313             .isEqualTo(usage);
314     }
315 
316     @Test
317     public void testNoConfigSpecified(@SysErr Capturable systemErr, @SysOut Capturable systemOut) {
318         assertMainReturnCode(-1, getPath("InputMain.java"));
319         assertWithMessage("Unexpected output log")
320             .that(systemOut.getCapturedData())
321             .isEqualTo("Must specify a config XML file." + System.lineSeparator());
322         assertWithMessage("Unexpected system error log")
323             .that(systemErr.getCapturedData())
324             .isEqualTo("");
325     }
326 
327     @Test
328     public void testNonExistentTargetFile(@SysErr Capturable systemErr,
329             @SysOut Capturable systemOut) {
330         assertMainReturnCode(-1, "-c", "/google_checks.xml", "NonExistentFile.java");
331         assertWithMessage("Unexpected output log")
332             .that(systemOut.getCapturedData())
333             .isEqualTo("Files to process must be specified, found 0." + System.lineSeparator());
334         assertWithMessage("Unexpected system error log")
335             .that(systemErr.getCapturedData())
336             .isEqualTo("");
337     }
338 
339     @Test
340     public void testExistingTargetFileButWithoutReadAccess(
341             @SysErr Capturable systemErr, @SysOut Capturable systemOut) throws IOException {
342         final File file = Files.createTempFile(temporaryFolder.toPath(),
343                 "testExistingTargetFileButWithoutReadAccess", null).toFile();
344         // skip execution if file is still readable, it is possible on some Windows machines
345         // see https://github.com/checkstyle/checkstyle/issues/7032 for details
346         assumeTrue(file.setReadable(false), "file is still readable");
347 
348         final String canonicalPath = file.getCanonicalPath();
349         assertMainReturnCode(-1, "-c", "/google_checks.xml", canonicalPath);
350         assertWithMessage("Unexpected output log")
351             .that(systemOut.getCapturedData())
352             .isEqualTo("Files to process must be specified, found 0." + System.lineSeparator());
353         assertWithMessage("Unexpected system error log")
354             .that(systemErr.getCapturedData())
355             .isEqualTo("");
356     }
357 
358     @Test
359     public void testCustomSeverityVariableForGoogleConfig(@SysOut Capturable systemOut) {
360         assertMainReturnCode(1, "-c", "/google_checks.xml",
361                 "-p", getPath("InputMainCustomSeverityForGoogleConfig.properties"),
362                 getPath("InputMainCustomSeverityForGoogleConfig.java"));
363 
364         final String expectedOutputStart = addEndOfLine(auditStartMessage.getMessage())
365             + "[ERROR] ";
366         final String expectedOutputEnd = addEndOfLine(
367                 "InputMainCustomSeverityForGoogleConfig.java:3:1:"
368                     + " Missing a Javadoc comment. [MissingJavadocType]",
369                 auditFinishMessage.getMessage());
370         assertWithMessage("Unexpected output log")
371             .that(systemOut.getCapturedData())
372             .startsWith(expectedOutputStart);
373         assertWithMessage("Unexpected output log")
374             .that(systemOut.getCapturedData())
375             .endsWith(expectedOutputEnd);
376     }
377 
378     @Test
379     public void testDefaultSeverityVariableForGoogleConfig(@SysOut Capturable systemOut) {
380         assertMainReturnCode(0, "-c", "/google_checks.xml",
381                 getPath("InputMainCustomSeverityForGoogleConfig.java"));
382 
383         final String expectedOutputStart = addEndOfLine(auditStartMessage.getMessage())
384                 + "[WARN] ";
385         final String expectedOutputEnd = addEndOfLine(
386                 "InputMainCustomSeverityForGoogleConfig.java:3:1:"
387                         + " Missing a Javadoc comment. [MissingJavadocType]",
388                 auditFinishMessage.getMessage());
389         assertWithMessage("Unexpected output log")
390                 .that(systemOut.getCapturedData())
391                 .startsWith(expectedOutputStart);
392         assertWithMessage("Unexpected output log")
393                 .that(systemOut.getCapturedData())
394                 .endsWith(expectedOutputEnd);
395     }
396 
397     @Test
398     public void testNonExistentConfigFile(@SysErr Capturable systemErr,
399             @SysOut Capturable systemOut) {
400         assertMainReturnCode(-1, "-c", "src/main/resources/non_existent_config.xml",
401                     getPath("InputMain.java"));
402         assertWithMessage("Unexpected output log")
403             .that(systemOut.getCapturedData())
404             .isEqualTo(addEndOfLine("Could not find config XML file "
405                     + "'src/main/resources/non_existent_config.xml'."));
406         assertWithMessage("Unexpected system error log")
407             .that(systemErr.getCapturedData())
408             .isEqualTo("");
409     }
410 
411     @Test
412     public void testNonExistentOutputFormat(@SysErr Capturable systemErr,
413             @SysOut Capturable systemOut) {
414         assertMainReturnCode(-1, "-c", "/google_checks.xml", "-f", "xmlp",
415                 getPath("InputMain.java"));
416         assertWithMessage("Unexpected output log")
417             .that(systemOut.getCapturedData())
418             .isEqualTo("");
419         assertWithMessage("Unexpected system error log")
420             .that(systemErr.getCapturedData())
421             .isEqualTo("Invalid value for option '-f': expected one of [XML, SARIF, PLAIN]"
422                     + " (case-insensitive) but was 'xmlp'" + EOL + SHORT_USAGE);
423     }
424 
425     @Test
426     public void testNonExistentClass(@SysErr Capturable systemErr) {
427         assertMainReturnCode(-2, "-c", getPath("InputMainConfig-non-existent-classname.xml"),
428                     getPath("InputMain.java"));
429         final String cause = "com.puppycrawl.tools.checkstyle.api.CheckstyleException:"
430                 + " cannot initialize module TreeWalker - ";
431         assertWithMessage("Unexpected system error log")
432                 .that(systemErr.getCapturedData())
433                 .startsWith(cause);
434     }
435 
436     @Test
437     public void testExistingTargetFile(@SysErr Capturable systemErr, @SysOut Capturable systemOut) {
438         assertMainReturnCode(0, "-c", getPath("InputMainConfig-classname.xml"),
439                 getPath("InputMain.java"));
440         assertWithMessage("Unexpected output log")
441             .that(systemOut.getCapturedData())
442             .isEqualTo(addEndOfLine(auditStartMessage.getMessage(),
443                 auditFinishMessage.getMessage()));
444         assertWithMessage("Unexpected system error log")
445             .that(systemErr.getCapturedData())
446             .isEqualTo("");
447     }
448 
449     @Test
450     public void testExistingTargetFileXmlOutput(@SysErr Capturable systemErr,
451             @SysOut Capturable systemOut) throws IOException {
452         assertMainReturnCode(0, "-c", getPath("InputMainConfig-classname.xml"), "-f", "xml",
453                 getPath("InputMain.java"));
454         final String expectedPath = getFilePath("InputMain.java");
455         final String version = Main.class.getPackage().getImplementationVersion();
456         assertWithMessage("Unexpected output log")
457             .that(systemOut.getCapturedData())
458             .isEqualTo(addEndOfLine(
459             "<?xml version=\"1.0\" encoding=\"UTF-8\"?>",
460                 "<checkstyle version=\"" + version + "\">",
461                 "<file name=\"" + expectedPath + "\">",
462                 "</file>",
463                 "</checkstyle>"));
464         assertWithMessage("Unexpected system error log")
465             .that(systemErr.getCapturedData())
466             .isEqualTo("");
467     }
468 
469     /**
470      * This test method is created only to cover
471      * pitest mutation survival at {@code Main#getOutputStreamOptions}.
472      * Parameters {@code systemErr} and {@code systemOut} are used to restore
473      * the original system streams.
474      *
475      * @param systemErr the system error stream
476      * @param systemOut the system output stream
477      */
478     @Test
479     public void testNonClosedSystemStreams(@SysErr Capturable systemErr,
480            @SysOut Capturable systemOut) {
481         try (ShouldNotBeClosedStream stream = new ShouldNotBeClosedStream()) {
482             System.setOut(stream);
483             System.setErr(stream);
484             assertMainReturnCode(0, "-c", getPath("InputMainConfig-classname.xml"), "-f", "xml",
485                     getPath("InputMain.java"));
486             assertWithMessage("stream should not be closed")
487                 .that(stream.isClosed)
488                 .isFalse();
489             assertWithMessage("System.err should be not used")
490                 .that(systemErr.getCapturedData())
491                 .isEmpty();
492             assertWithMessage("System.out should be not used")
493                 .that(systemOut.getCapturedData())
494                 .isEmpty();
495         }
496     }
497 
498     /**
499      * This test method is created only to cover
500      * pitest mutation survival at Main#getOutputStreamOptions.
501      * No ability to test it by out general tests.
502      * It is hard test that inner stream is closed, so pure UT is created to validate result
503      * of private method Main.getOutputStreamOptions
504      *
505      * @throws Exception if there is an error.
506      */
507     @Test
508     public void testGetOutputStreamOptionsMethod() throws Exception {
509         final Path path = new File(getPath("InputMain.java")).toPath();
510         final OutputStreamOptions option =
511                 TestUtil.invokeStaticMethod(Main.class, "getOutputStreamOptions", path);
512         assertWithMessage("Main.getOutputStreamOptions return CLOSE on not null Path")
513                 .that(option)
514                 .isEqualTo(OutputStreamOptions.CLOSE);
515     }
516 
517     @Test
518     public void testExistingTargetFilePlainOutput(@SysErr Capturable systemErr,
519             @SysOut Capturable systemOut) {
520         assertMainReturnCode(0, "-c", getPath("InputMainConfig-classname.xml"), "-f", "plain",
521                 getPath("InputMain.java"));
522         assertWithMessage("Unexpected output log")
523             .that(systemOut.getCapturedData())
524             .isEqualTo(addEndOfLine(auditStartMessage.getMessage(),
525                 auditFinishMessage.getMessage()));
526         assertWithMessage("Unexpected system error log")
527             .that(systemErr.getCapturedData())
528             .isEqualTo("");
529     }
530 
531     @Test
532     public void testExistingTargetFileWithViolations(@SysErr Capturable systemErr,
533             @SysOut Capturable systemOut) throws IOException {
534         assertMainReturnCode(0, "-c", getPath("InputMainConfig-classname2.xml"),
535                 getPath("InputMain.java"));
536         final Violation invalidPatternMessageMain = new Violation(1,
537                 "com.puppycrawl.tools.checkstyle.checks.naming.messages",
538                 "name.invalidPattern", new String[] {"InputMain", "^[a-z0-9]*$"},
539                 null, getClass(), null);
540         final Violation invalidPatternMessageMainInner = new Violation(1,
541                 "com.puppycrawl.tools.checkstyle.checks.naming.messages",
542                 "name.invalidPattern", new String[] {"InputMainInner", "^[a-z0-9]*$"},
543                 null, getClass(), null);
544         final String expectedPath = getFilePath("InputMain.java");
545         assertWithMessage("Unexpected output log")
546             .that(systemOut.getCapturedData())
547             .isEqualTo(addEndOfLine(auditStartMessage.getMessage(),
548                     "[WARN] " + expectedPath + ":3:14: "
549                         + invalidPatternMessageMain.getViolation()
550                         + " [TypeName]",
551                     "[WARN] " + expectedPath + ":5:7: "
552                         + invalidPatternMessageMainInner.getViolation()
553                         + " [TypeName]",
554                     auditFinishMessage.getMessage()));
555         assertWithMessage("Unexpected system error log")
556             .that(systemErr.getCapturedData())
557             .isEqualTo("");
558     }
559 
560     @Test
561     public void testViolationsByGoogleAndXpathSuppressions(@SysErr Capturable systemErr,
562             @SysOut Capturable systemOut) {
563         System.setProperty("org.checkstyle.google.suppressionxpathfilter.config",
564                 getPath("InputMainViolationsForGoogleXpathSuppressions.xml"));
565         assertMainReturnCode(0, "-c", "/google_checks.xml",
566                 getPath("InputMainViolationsForGoogle.java"));
567         assertWithMessage("Unexpected output log")
568             .that(systemOut.getCapturedData())
569             .isEqualTo(noViolationsOutput);
570         assertWithMessage("Unexpected system error log")
571             .that(systemErr.getCapturedData())
572             .isEqualTo("");
573     }
574 
575     @Test
576     public void testViolationsByGoogleAndSuppressions(@SysErr Capturable systemErr,
577             @SysOut Capturable systemOut) {
578         System.setProperty("org.checkstyle.google.suppressionfilter.config",
579                 getPath("InputMainViolationsForGoogleSuppressions.xml"));
580         assertMainReturnCode(0, "-c", "/google_checks.xml",
581                 getPath("InputMainViolationsForGoogle.java"));
582         assertWithMessage("Unexpected output log")
583             .that(systemOut.getCapturedData())
584             .isEqualTo(noViolationsOutput);
585         assertWithMessage("Unexpected system error log")
586             .that(systemErr.getCapturedData())
587             .isEqualTo("");
588     }
589 
590     @Test
591     public void testExistingTargetFileWithError(@SysErr Capturable systemErr,
592             @SysOut Capturable systemOut) throws Exception {
593         assertMainReturnCode(2, "-c", getPath("InputMainConfig-classname2-error.xml"),
594                     getPath("InputMain.java"));
595         final Violation errorCounterTwoMessage = new Violation(1,
596                 Definitions.CHECKSTYLE_BUNDLE, Main.ERROR_COUNTER,
597                 new String[] {String.valueOf(2)}, null, getClass(), null);
598         final Violation invalidPatternMessageMain = new Violation(1,
599                 "com.puppycrawl.tools.checkstyle.checks.naming.messages",
600                 "name.invalidPattern", new String[] {"InputMain", "^[a-z0-9]*$"},
601                 null, getClass(), null);
602         final Violation invalidPatternMessageMainInner = new Violation(1,
603                 "com.puppycrawl.tools.checkstyle.checks.naming.messages",
604                 "name.invalidPattern", new String[] {"InputMainInner", "^[a-z0-9]*$"},
605                 null, getClass(), null);
606         final String expectedPath = getFilePath("InputMain.java");
607         assertWithMessage("Unexpected output log")
608             .that(systemOut.getCapturedData())
609             .isEqualTo(addEndOfLine(auditStartMessage.getMessage(),
610                     "[ERROR] " + expectedPath + ":3:14: "
611                         + invalidPatternMessageMain.getViolation() + " [TypeName]",
612                     "[ERROR] " + expectedPath + ":5:7: "
613                         + invalidPatternMessageMainInner.getViolation() + " [TypeName]",
614                     auditFinishMessage.getMessage()));
615         assertWithMessage("Unexpected system error log")
616             .that(systemErr.getCapturedData())
617             .isEqualTo(addEndOfLine(errorCounterTwoMessage.getViolation()));
618     }
619 
620     /**
621      * Similar test to {@link #testExistingTargetFileWithError}, but for PIT mutation tests:
622      * this test fails if the boundary condition is changed from {@code if (exitStatus > 0)}
623      * to {@code if (exitStatus > 1)}.
624      *
625      * @throws Exception should not throw anything
626      */
627     @Test
628     public void testExistingTargetFileWithOneError(@SysErr Capturable systemErr,
629             @SysOut Capturable systemOut) throws Exception {
630         assertMainReturnCode(1, "-c", getPath("InputMainConfig-classname2-error.xml"),
631                     getPath("InputMain1.java"));
632         final Violation errorCounterTwoMessage = new Violation(1,
633                 Definitions.CHECKSTYLE_BUNDLE, Main.ERROR_COUNTER,
634                 new String[] {String.valueOf(1)}, null, getClass(), null);
635         final Violation invalidPatternMessageMain = new Violation(1,
636                 "com.puppycrawl.tools.checkstyle.checks.naming.messages",
637                 "name.invalidPattern", new String[] {"InputMain1", "^[a-z0-9]*$"},
638                 null, getClass(), null);
639         final String expectedPath = getFilePath("InputMain1.java");
640         assertWithMessage("Unexpected output log")
641             .that(systemOut.getCapturedData())
642             .isEqualTo(addEndOfLine(auditStartMessage.getMessage(),
643                     "[ERROR] " + expectedPath + ":3:14: "
644                         + invalidPatternMessageMain.getViolation() + " [TypeName]",
645                     auditFinishMessage.getMessage()));
646         assertWithMessage("Unexpected system error log")
647             .that(systemErr.getCapturedData())
648             .isEqualTo(addEndOfLine(errorCounterTwoMessage.getViolation()));
649     }
650 
651     @Test
652     public void testExistingTargetFileWithOneErrorAgainstSunCheck(@SysErr Capturable systemErr,
653             @SysOut Capturable systemOut) throws Exception {
654         assertMainReturnCode(1, "-c", "/sun_checks.xml", getPath("InputMain1.java"));
655         final Violation errorCounterTwoMessage = new Violation(1,
656                 Definitions.CHECKSTYLE_BUNDLE, Main.ERROR_COUNTER,
657                 new String[] {String.valueOf(1)}, null, getClass(), null);
658         final Violation message = new Violation(1,
659                 "com.puppycrawl.tools.checkstyle.checks.javadoc.messages",
660                 "javadoc.packageInfo", new String[] {},
661                 null, getClass(), null);
662         final String expectedPath = getFilePath("InputMain1.java");
663         assertWithMessage("Unexpected output log")
664             .that(systemOut.getCapturedData())
665             .isEqualTo(addEndOfLine(auditStartMessage.getMessage(),
666                 "[ERROR] " + expectedPath + ":1: " + message.getViolation() + " [JavadocPackage]",
667                 auditFinishMessage.getMessage()));
668         assertWithMessage("Unexpected system error log")
669             .that(systemErr.getCapturedData())
670             .isEqualTo(addEndOfLine(errorCounterTwoMessage.getViolation()));
671     }
672 
673     @Test
674     public void testExistentTargetFilePlainOutputToNonExistentFile(@SysErr Capturable systemErr,
675             @SysOut Capturable systemOut) {
676         assertMainReturnCode(0, "-c", getPath("InputMainConfig-classname.xml"), "-f", "plain",
677                 "-o", temporaryFolder + "/output.txt", getPath("InputMain.java"));
678         assertWithMessage("Unexpected output log")
679             .that(systemOut.getCapturedData())
680             .isEqualTo("");
681         assertWithMessage("Unexpected system error log")
682             .that(systemErr.getCapturedData())
683             .isEqualTo("");
684     }
685 
686     @Test
687     public void testExistingTargetFilePlainOutputToFile(@SysErr Capturable systemErr,
688             @SysOut Capturable systemOut) throws Exception {
689         final String outputFile =
690                 Files.createTempFile(temporaryFolder.toPath(), "file", ".output").toFile()
691                     .getCanonicalPath();
692         assertWithMessage("File must exist")
693                 .that(new File(outputFile).exists())
694                 .isTrue();
695         assertMainReturnCode(0, "-c", getPath("InputMainConfig-classname.xml"), "-f", "plain",
696                 "-o", outputFile, getPath("InputMain.java"));
697         assertWithMessage("Unexpected output log")
698             .that(systemOut.getCapturedData())
699             .isEqualTo("");
700         assertWithMessage("Unexpected system error log")
701             .that(systemErr.getCapturedData())
702             .isEqualTo("");
703     }
704 
705     @Test
706     public void testCreateNonExistentOutputFile() throws IOException {
707         final String outputFile = new File(temporaryFolder, "nonexistent.out").getCanonicalPath();
708         assertWithMessage("File must not exist")
709                 .that(new File(outputFile).exists())
710                 .isFalse();
711         assertMainReturnCode(0, "-c", getPath("InputMainConfig-classname.xml"), "-f", "plain",
712                 "-o", outputFile, getPath("InputMain.java"));
713         assertWithMessage("File must exist")
714                 .that(new File(outputFile).exists())
715                 .isTrue();
716     }
717 
718     @Test
719     public void testExistingTargetFilePlainOutputProperties(@SysErr Capturable systemErr,
720             @SysOut Capturable systemOut) {
721         assertMainReturnCode(0, "-c", getPath("InputMainConfig-classname-prop.xml"),
722                 "-p", getPath("InputMainMycheckstyle.properties"), getPath("InputMain.java"));
723         assertWithMessage("Unexpected output log")
724             .that(systemOut.getCapturedData())
725             .isEqualTo(addEndOfLine(auditStartMessage.getMessage(),
726                 auditFinishMessage.getMessage()));
727         assertWithMessage("Unexpected system error log")
728             .that(systemErr.getCapturedData())
729             .isEqualTo("");
730     }
731 
732     @Test
733     public void testPropertyFileWithPropertyChaining(@SysErr Capturable systemErr,
734             @SysOut Capturable systemOut) {
735         assertMainReturnCode(0, "-c", getPath("InputMainConfig-classname-prop.xml"),
736             "-p", getPath("InputMainPropertyChaining.properties"), getPath("InputMain.java"));
737 
738         assertWithMessage("Unexpected output log")
739             .that(systemOut.getCapturedData())
740             .isEqualTo(addEndOfLine(auditStartMessage.getMessage(),
741                 auditFinishMessage.getMessage()));
742         assertWithMessage("Unexpected system error log")
743             .that(systemErr.getCapturedData())
744             .isEqualTo("");
745     }
746 
747     @Test
748     public void testPropertyFileWithPropertyChainingUndefinedProperty(@SysErr Capturable systemErr,
749             @SysOut Capturable systemOut) {
750         assertMainReturnCode(-2, "-c", getPath("InputMainConfig-classname-prop.xml"),
751                 "-p", getPath("InputMainPropertyChainingUndefinedProperty.properties"),
752                 getPath("InputMain.java"));
753 
754         assertWithMessage("Invalid error message")
755             .that(systemErr.getCapturedData())
756             .contains(ChainedPropertyUtil.UNDEFINED_PROPERTY_MESSAGE);
757         assertWithMessage("Unexpected output log")
758             .that(systemOut.getCapturedData())
759             .isEqualTo("");
760     }
761 
762     @Test
763     public void testExistingTargetFilePlainOutputNonexistentProperties(@SysErr Capturable systemErr,
764             @SysOut Capturable systemOut) {
765         assertMainReturnCode(-1, "-c", getPath("InputMainConfig-classname-prop.xml"),
766                     "-p", "nonexistent.properties", getPath("InputMain.java"));
767         assertWithMessage("Unexpected output log")
768             .that(systemOut.getCapturedData())
769             .isEqualTo("Could not find file 'nonexistent.properties'."
770                 + System.lineSeparator());
771         assertWithMessage("Unexpected system error log")
772             .that(systemErr.getCapturedData())
773             .isEqualTo("");
774     }
775 
776     @Test
777     public void testExistingIncorrectConfigFile(@SysErr Capturable systemErr) {
778         assertMainReturnCode(-2, "-c", getPath("InputMainConfig-Incorrect.xml"),
779                 getPath("InputMain.java"));
780         final String errorOutput = "com.puppycrawl.tools.checkstyle.api."
781             + "CheckstyleException: unable to parse configuration stream - ";
782         assertWithMessage("Unexpected system error log")
783                 .that(systemErr.getCapturedData())
784                 .startsWith(errorOutput);
785     }
786 
787     @Test
788     public void testExistingIncorrectChildrenInConfigFile(@SysErr Capturable systemErr) {
789         assertMainReturnCode(-2, "-c", getPath("InputMainConfig-incorrectChildren.xml"),
790                     getPath("InputMain.java"));
791         final String errorOutput = "com.puppycrawl.tools.checkstyle.api."
792                 + "CheckstyleException: cannot initialize module RegexpSingleline"
793                 + " - RegexpSingleline is not allowed as a child in RegexpSingleline";
794         assertWithMessage("Unexpected system error log")
795                 .that(systemErr.getCapturedData())
796                 .startsWith(errorOutput);
797     }
798 
799     @Test
800     public void testExistingIncorrectChildrenInConfigFile2(@SysErr Capturable systemErr) {
801         assertMainReturnCode(-2, "-c", getPath("InputMainConfig-incorrectChildren2.xml"),
802                     getPath("InputMain.java"));
803         final String errorOutput = "com.puppycrawl.tools.checkstyle.api."
804                 + "CheckstyleException: cannot initialize module TreeWalker - "
805                 + "cannot initialize module JavadocMethod - "
806                 + "JavadocVariable is not allowed as a child in JavadocMethod";
807         assertWithMessage("Unexpected system error log")
808                 .that(systemErr.getCapturedData())
809                 .startsWith(errorOutput);
810     }
811 
812     @Test
813     public void testLoadPropertiesIoException() throws Exception {
814         final Class<?> cliOptionsClass = Class.forName(Main.class.getName());
815         try {
816             TestUtil.invokeStaticMethod(cliOptionsClass, "loadProperties", new File("."));
817             assertWithMessage("Exception was expected").fail();
818         }
819         catch (ReflectiveOperationException exc) {
820             assertWithMessage("Invalid error cause")
821                     .that(exc)
822                     .hasCauseThat()
823                     .isInstanceOf(CheckstyleException.class);
824             // We do separate validation for message as in Windows
825             // disk drive letter appear in message,
826             // so we skip that drive letter for compatibility issues
827             final Violation loadPropertiesMessage = new Violation(1,
828                     Definitions.CHECKSTYLE_BUNDLE, Main.LOAD_PROPERTIES_EXCEPTION,
829                     new String[] {""}, null, getClass(), null);
830             final String causeMessage = exc.getCause().getLocalizedMessage();
831             final String violation = loadPropertiesMessage.getViolation();
832             final boolean samePrefix = causeMessage.substring(0, causeMessage.indexOf(' '))
833                     .equals(violation
834                             .substring(0, violation.indexOf(' ')));
835             final boolean sameSuffix =
836                     causeMessage.substring(causeMessage.lastIndexOf(' '))
837                     .equals(violation
838                             .substring(violation.lastIndexOf(' ')));
839             assertWithMessage("Invalid violation")
840                     .that(samePrefix || sameSuffix)
841                     .isTrue();
842             assertWithMessage("Invalid violation")
843                     .that(causeMessage)
844                     .contains(".'");
845         }
846     }
847 
848     @Test
849     public void testExistingDirectoryWithViolations(@SysErr Capturable systemErr,
850             @SysOut Capturable systemOut) throws IOException {
851         // we just reference there all violations
852         final String[][] outputValues = {
853                 {"InputMainComplexityOverflow", "1", "172"},
854         };
855 
856         final int allowedLength = 170;
857         final String msgKey = "maxLen.file";
858         final String bundle = "com.puppycrawl.tools.checkstyle.checks.sizes.messages";
859 
860         assertMainReturnCode(0, "-c", getPath("InputMainConfig-filelength.xml"),
861                 getPath(""));
862         final String expectedPath = getFilePath("") + File.separator;
863         final StringBuilder sb = new StringBuilder(28);
864         sb.append(auditStartMessage.getMessage())
865                 .append(EOL);
866         final String format = "[WARN] " + expectedPath + outputValues[0][0] + ".java:"
867                 + outputValues[0][1] + ": ";
868         for (String[] outputValue : outputValues) {
869             final String violation = new Violation(1, bundle,
870                     msgKey, new Integer[] {Integer.valueOf(outputValue[2]), allowedLength},
871                     null, getClass(), null).getViolation();
872             final String line = format + violation + " [FileLength]";
873             sb.append(line).append(EOL);
874         }
875         sb.append(auditFinishMessage.getMessage())
876                 .append(EOL);
877         assertWithMessage("Unexpected output log")
878             .that(systemOut.getCapturedData())
879             .isEqualTo(sb.toString());
880         assertWithMessage("Unexpected system error log")
881             .that(systemErr.getCapturedData())
882             .isEqualTo("");
883     }
884 
885     /**
886      * Test doesn't need to be serialized.
887      *
888      * @noinspection SerializableInnerClassWithNonSerializableOuterClass
889      * @noinspectionreason SerializableInnerClassWithNonSerializableOuterClass - mocked file
890      *      for test does not require serialization
891      */
892     @Test
893     public void testListFilesNotFile() throws Exception {
894         final File fileMock = new File("") {
895             @Serial
896             private static final long serialVersionUID = 1L;
897 
898             @Override
899             public boolean canRead() {
900                 return true;
901             }
902 
903             @Override
904             public boolean isDirectory() {
905                 return false;
906             }
907 
908             @Override
909             public boolean isFile() {
910                 return false;
911             }
912         };
913 
914         final List<File> result = TestUtil.invokeStaticMethod(Main.class, "listFiles",
915                 fileMock, new ArrayList<>());
916         assertWithMessage("Invalid result size")
917             .that(result)
918             .isEmpty();
919     }
920 
921     /**
922      * Test doesn't need to be serialized.
923      *
924      * @noinspection SerializableInnerClassWithNonSerializableOuterClass
925      * @noinspectionreason SerializableInnerClassWithNonSerializableOuterClass - mocked file
926      *      for test does not require serialization
927      */
928     @Test
929     public void testListFilesDirectoryWithNull() throws Exception {
930         final File[] nullResult = null;
931         final File fileMock = new File("") {
932             @Serial
933             private static final long serialVersionUID = 1L;
934 
935             @Override
936             public boolean canRead() {
937                 return true;
938             }
939 
940             @Override
941             public boolean isDirectory() {
942                 return true;
943             }
944 
945             @Override
946             public File[] listFiles() {
947                 return nullResult;
948             }
949         };
950 
951         final List<File> result = TestUtil.invokeStaticMethod(Main.class, "listFiles",
952                 fileMock, new ArrayList<>());
953         assertWithMessage("Invalid result size")
954             .that(result)
955             .isEmpty();
956     }
957 
958     @Test
959     public void testFileReferenceDuringException(@SysErr Capturable systemErr) {
960         // We put xml as source to cause parse exception
961         assertMainReturnCode(-2, "-c", getPath("InputMainConfig-classname.xml"),
962                     getNonCompilablePath("InputMainIncorrectClass.java"));
963         final String exceptionMessage = addEndOfLine("com.puppycrawl.tools.checkstyle.api."
964                 + "CheckstyleException: Exception was thrown while processing "
965                 + new File(getNonCompilablePath("InputMainIncorrectClass.java")).getPath());
966         assertWithMessage("Unexpected system error log")
967                 .that(systemErr.getCapturedData())
968                 .contains(exceptionMessage);
969     }
970 
971     @Test
972     public void testRemoveLexerDefaultErrorListener(@SysErr Capturable systemErr) {
973         assertMainReturnCode(-2, "-t", getNonCompilablePath("InputMainIncorrectClass.java"));
974 
975         assertWithMessage("First line of exception message should not contain lexer error.")
976             .that(systemErr.getCapturedData().startsWith("line 2:2 token recognition error"))
977                 .isFalse();
978     }
979 
980     @Test
981     public void testRemoveParserDefaultErrorListener(@SysErr Capturable systemErr) {
982         assertMainReturnCode(-2, "-t", getNonCompilablePath("InputMainIncorrectClass.java"));
983         final String capturedData = systemErr.getCapturedData();
984 
985         assertWithMessage("First line of exception message should not contain parser error.")
986             .that(capturedData.startsWith("line 2:0 no viable alternative"))
987                 .isFalse();
988         assertWithMessage("Second line of exception message should not contain parser error.")
989             .that(capturedData.startsWith("line 2:0 no viable alternative",
990                     capturedData.indexOf('\n') + 1))
991                 .isFalse();
992     }
993 
994     @Test
995     public void testPrintTreeOnMoreThanOneFile(@SysErr Capturable systemErr,
996             @SysOut Capturable systemOut) {
997         assertMainReturnCode(-1, "-t", getPath(""));
998         assertWithMessage("Unexpected output log")
999             .that(systemOut.getCapturedData())
1000             .isEqualTo("Printing AST is allowed for only one file." + System.lineSeparator());
1001         assertWithMessage("Unexpected system error log")
1002             .that(systemErr.getCapturedData())
1003             .isEqualTo("");
1004     }
1005 
1006     @Test
1007     public void testPrintTreeOption(@SysErr Capturable systemErr, @SysOut Capturable systemOut) {
1008         final String expected = addEndOfLine(
1009             "COMPILATION_UNIT -> COMPILATION_UNIT [1:0]",
1010             "|--PACKAGE_DEF -> package [1:0]",
1011             "|   |--ANNOTATIONS -> ANNOTATIONS [1:39]",
1012             "|   |--DOT -> . [1:39]",
1013             "|   |   |--DOT -> . [1:28]",
1014             "|   |   |   |--DOT -> . [1:22]",
1015             "|   |   |   |   |--DOT -> . [1:11]",
1016             "|   |   |   |   |   |--IDENT -> com [1:8]",
1017             "|   |   |   |   |   `--IDENT -> puppycrawl [1:12]",
1018             "|   |   |   |   `--IDENT -> tools [1:23]",
1019             "|   |   |   `--IDENT -> checkstyle [1:29]",
1020             "|   |   `--IDENT -> main [1:40]",
1021             "|   `--SEMI -> ; [1:44]",
1022             "|--CLASS_DEF -> CLASS_DEF [3:0]",
1023             "|   |--MODIFIERS -> MODIFIERS [3:0]",
1024             "|   |   `--LITERAL_PUBLIC -> public [3:0]",
1025             "|   |--LITERAL_CLASS -> class [3:7]",
1026             "|   |--IDENT -> InputMain [3:13]",
1027             "|   `--OBJBLOCK -> OBJBLOCK [3:23]",
1028             "|       |--LCURLY -> { [3:23]",
1029             "|       `--RCURLY -> } [4:0]",
1030             "`--CLASS_DEF -> CLASS_DEF [5:0]",
1031             "    |--MODIFIERS -> MODIFIERS [5:0]",
1032             "    |--LITERAL_CLASS -> class [5:0]",
1033             "    |--IDENT -> InputMainInner [5:6]",
1034             "    `--OBJBLOCK -> OBJBLOCK [5:21]",
1035             "        |--LCURLY -> { [5:21]",
1036             "        `--RCURLY -> } [6:0]");
1037 
1038         assertMainReturnCode(0, "-t", getPath("InputMain.java"));
1039         assertWithMessage("Unexpected output log")
1040             .that(systemOut.getCapturedData())
1041             .isEqualTo(expected);
1042         assertWithMessage("Unexpected system error log")
1043             .that(systemErr.getCapturedData())
1044             .isEqualTo("");
1045     }
1046 
1047     @Test
1048     public void testPrintXpathOption(@SysErr Capturable systemErr, @SysOut Capturable systemOut) {
1049         final String expected = addEndOfLine(
1050             "COMPILATION_UNIT -> COMPILATION_UNIT [1:0]",
1051             "|--CLASS_DEF -> CLASS_DEF [3:0]",
1052             "|   `--OBJBLOCK -> OBJBLOCK [3:28]",
1053             "|       |--METHOD_DEF -> METHOD_DEF [4:4]",
1054             "|       |   `--SLIST -> { [4:20]",
1055             "|       |       |--VARIABLE_DEF -> VARIABLE_DEF [5:8]",
1056             "|       |       |   |--IDENT -> a [5:12]");
1057         assertMainReturnCode(0, "-b",
1058                 "/COMPILATION_UNIT/CLASS_DEF//METHOD_DEF[./IDENT[@text='methodOne']]"
1059                         + "//VARIABLE_DEF/IDENT",
1060                 getPath("InputMainXPath.java"));
1061         assertWithMessage("Unexpected output log")
1062             .that(systemOut.getCapturedData())
1063             .isEqualTo(expected);
1064         assertWithMessage("Unexpected system error log")
1065             .that(systemErr.getCapturedData())
1066             .isEqualTo("");
1067     }
1068 
1069     @Test
1070     public void testPrintXpathCommentNode(@SysErr Capturable systemErr,
1071             @SysOut Capturable systemOut) {
1072         final String expected = addEndOfLine(
1073             "COMPILATION_UNIT -> COMPILATION_UNIT [1:0]",
1074             "`--CLASS_DEF -> CLASS_DEF [17:0]",
1075             "    `--OBJBLOCK -> OBJBLOCK [17:19]",
1076             "        |--CTOR_DEF -> CTOR_DEF [19:4]",
1077             "        |   |--BLOCK_COMMENT_BEGIN -> /* [18:4]");
1078         assertMainReturnCode(0, "-b", "/COMPILATION_UNIT/CLASS_DEF//BLOCK_COMMENT_BEGIN",
1079                 getPath("InputMainXPath.java"));
1080         assertWithMessage("Unexpected output log")
1081             .that(systemOut.getCapturedData())
1082             .isEqualTo(expected);
1083         assertWithMessage("Unexpected system error log")
1084             .that(systemErr.getCapturedData())
1085             .isEqualTo("");
1086     }
1087 
1088     @Test
1089     public void testPrintXpathNodeParentNull(@SysErr Capturable systemErr,
1090             @SysOut Capturable systemOut) {
1091         final String expected = addEndOfLine("COMPILATION_UNIT -> COMPILATION_UNIT [1:0]");
1092         assertMainReturnCode(0, "-b", "/COMPILATION_UNIT", getPath("InputMainXPath.java"));
1093         assertWithMessage("Unexpected output log")
1094             .that(systemOut.getCapturedData())
1095             .isEqualTo(expected);
1096         assertWithMessage("Unexpected system error log")
1097             .that(systemErr.getCapturedData())
1098             .isEqualTo("");
1099     }
1100 
1101     @Test
1102     public void testPrintXpathFullOption(
1103             @SysErr Capturable systemErr, @SysOut Capturable systemOut) {
1104         final String expected = addEndOfLine(
1105             "COMPILATION_UNIT -> COMPILATION_UNIT [1:0]",
1106             "|--CLASS_DEF -> CLASS_DEF [3:0]",
1107             "|   `--OBJBLOCK -> OBJBLOCK [3:28]",
1108             "|       |--METHOD_DEF -> METHOD_DEF [8:4]",
1109             "|       |   `--SLIST -> { [8:26]",
1110             "|       |       |--VARIABLE_DEF -> VARIABLE_DEF [9:8]",
1111             "|       |       |   |--IDENT -> a [9:12]");
1112         final String xpath = "/COMPILATION_UNIT/CLASS_DEF//METHOD_DEF[./IDENT[@text='method']]"
1113                 + "//VARIABLE_DEF/IDENT";
1114         assertMainReturnCode(0, "--branch-matching-xpath", xpath, getPath("InputMainXPath.java"));
1115         assertWithMessage("Unexpected output log")
1116             .that(systemOut.getCapturedData())
1117             .isEqualTo(expected);
1118         assertWithMessage("Unexpected system error log")
1119             .that(systemErr.getCapturedData())
1120             .isEqualTo("");
1121     }
1122 
1123     @Test
1124     public void testPrintXpathTwoResults(
1125             @SysErr Capturable systemErr, @SysOut Capturable systemOut) {
1126         final String expected = addEndOfLine(
1127             "COMPILATION_UNIT -> COMPILATION_UNIT [1:0]",
1128             "|--CLASS_DEF -> CLASS_DEF [12:0]",
1129             "|   `--OBJBLOCK -> OBJBLOCK [12:10]",
1130             "|       |--METHOD_DEF -> METHOD_DEF [13:4]",
1131             "---------",
1132             "COMPILATION_UNIT -> COMPILATION_UNIT [1:0]",
1133             "|--CLASS_DEF -> CLASS_DEF [12:0]",
1134             "|   `--OBJBLOCK -> OBJBLOCK [12:10]",
1135             "|       |--METHOD_DEF -> METHOD_DEF [14:4]");
1136         assertMainReturnCode(0, "--branch-matching-xpath",
1137                 "/COMPILATION_UNIT/CLASS_DEF[./IDENT[@text='Two']]//METHOD_DEF",
1138                 getPath("InputMainXPath.java"));
1139         assertWithMessage("Unexpected output log")
1140             .that(systemOut.getCapturedData())
1141             .isEqualTo(expected);
1142         assertWithMessage("Unexpected system error log")
1143             .that(systemErr.getCapturedData())
1144             .isEqualTo("");
1145     }
1146 
1147     @Test
1148     public void testPrintXpathInvalidXpath(@SysErr Capturable systemErr) throws Exception {
1149         final String invalidXpath = "\\/COMPILATION_UNIT/CLASS_DEF[./IDENT[@text='Two']]"
1150                 + "//METHOD_DEF";
1151         final String filePath = getFilePath("InputMainXPath.java");
1152         assertMainReturnCode(-2, "--branch-matching-xpath", invalidXpath, filePath);
1153         final String exceptionFirstLine = addEndOfLine("com.puppycrawl.tools.checkstyle.api."
1154             + "CheckstyleException: Error during evaluation for xpath: " + invalidXpath
1155             + ", file: " + filePath);
1156         assertWithMessage("Unexpected system error log")
1157             .that(systemErr.getCapturedData())
1158             .startsWith(exceptionFirstLine);
1159     }
1160 
1161     @Test
1162     public void testPrintTreeCommentsOption(@SysErr Capturable systemErr,
1163             @SysOut Capturable systemOut) {
1164         final String expected = addEndOfLine(
1165             "COMPILATION_UNIT -> COMPILATION_UNIT [1:0]",
1166             "|--PACKAGE_DEF -> package [1:0]",
1167             "|   |--ANNOTATIONS -> ANNOTATIONS [1:39]",
1168             "|   |--DOT -> . [1:39]",
1169             "|   |   |--DOT -> . [1:28]",
1170             "|   |   |   |--DOT -> . [1:22]",
1171             "|   |   |   |   |--DOT -> . [1:11]",
1172             "|   |   |   |   |   |--IDENT -> com [1:8]",
1173             "|   |   |   |   |   `--IDENT -> puppycrawl [1:12]",
1174             "|   |   |   |   `--IDENT -> tools [1:23]",
1175             "|   |   |   `--IDENT -> checkstyle [1:29]",
1176             "|   |   `--IDENT -> main [1:40]",
1177             "|   `--SEMI -> ; [1:44]",
1178             "|--CLASS_DEF -> CLASS_DEF [3:0]",
1179             "|   |--MODIFIERS -> MODIFIERS [3:0]",
1180             "|   |   |--BLOCK_COMMENT_BEGIN -> /* [2:0]",
1181             "|   |   |   |--COMMENT_CONTENT -> comment [2:2]",
1182             "|   |   |   `--BLOCK_COMMENT_END -> */ [2:8]",
1183             "|   |   `--LITERAL_PUBLIC -> public [3:0]",
1184             "|   |--LITERAL_CLASS -> class [3:7]",
1185             "|   |--IDENT -> InputMain [3:13]",
1186             "|   `--OBJBLOCK -> OBJBLOCK [3:23]",
1187             "|       |--LCURLY -> { [3:23]",
1188             "|       `--RCURLY -> } [4:0]",
1189             "`--CLASS_DEF -> CLASS_DEF [5:0]",
1190             "    |--MODIFIERS -> MODIFIERS [5:0]",
1191             "    |--LITERAL_CLASS -> class [5:0]",
1192             "    |--IDENT -> InputMainInner [5:6]",
1193             "    `--OBJBLOCK -> OBJBLOCK [5:21]",
1194             "        |--LCURLY -> { [5:21]",
1195             "        `--RCURLY -> } [6:0]");
1196 
1197         assertMainReturnCode(0, "-T", getPath("InputMain.java"));
1198         assertWithMessage("Unexpected output log")
1199             .that(systemOut.getCapturedData())
1200             .isEqualTo(expected);
1201         assertWithMessage("Unexpected system error log")
1202             .that(systemErr.getCapturedData())
1203             .isEqualTo("");
1204     }
1205 
1206     /**
1207      * Verifies the output of the command line parameter "-j".
1208      *
1209      * @param systemErr wrapper for {@code System.err}
1210      * @param systemOut wrapper for {@code System.out}
1211      * @throws IOException if I/O exception occurs while reading the test input.
1212      * @noinspection RedundantThrows
1213      * @noinspectionreason RedundantThrows - false positive
1214      */
1215     @Test
1216     public void testPrintTreeJavadocOption(@SysErr Capturable systemErr,
1217             @SysOut Capturable systemOut) throws IOException {
1218         final String expected = Files.readString(Path.of(
1219             getPath("InputMainExpectedInputJavadocComment.txt")))
1220             .replaceAll("\\\\r\\\\n", "\\\\n").replaceAll("\r\n", "\n");
1221 
1222         assertMainReturnCode(0, "-j", getPath("InputMainJavadocComment.javadoc"));
1223         assertWithMessage("Unexpected output log")
1224             .that(systemOut.getCapturedData().replaceAll("\\\\r\\\\n", "\\\\n")
1225                         .replaceAll("\r\n", "\n"))
1226             .isEqualTo(expected);
1227         assertWithMessage("Unexpected system error log")
1228             .that(systemErr.getCapturedData())
1229             .isEqualTo("");
1230     }
1231 
1232     @Test
1233     public void testPrintSuppressionOption(@SysErr Capturable systemErr,
1234             @SysOut Capturable systemOut) {
1235         final String expected = addEndOfLine(
1236             "/COMPILATION_UNIT/CLASS_DEF[./IDENT[@text='InputMainSuppressionsStringPrinter']]",
1237                 "/COMPILATION_UNIT/CLASS_DEF[./IDENT[@text='InputMainSuppressionsStringPrinter']]"
1238                         + "/MODIFIERS",
1239                 "/COMPILATION_UNIT/CLASS_DEF[./IDENT[@text='InputMainSuppressionsStringPrinter']"
1240                         + "]/LITERAL_CLASS");
1241 
1242         assertMainReturnCode(0, getPath("InputMainSuppressionsStringPrinter.java"), "-s", "3:1");
1243         assertWithMessage("Unexpected output log")
1244             .that(systemOut.getCapturedData())
1245             .isEqualTo(expected);
1246         assertWithMessage("Unexpected system error log")
1247             .that(systemErr.getCapturedData())
1248             .isEqualTo("");
1249     }
1250 
1251     @Test
1252     public void testPrintSuppressionAndTabWidthOption(@SysErr Capturable systemErr,
1253             @SysOut Capturable systemOut) {
1254         final String expected = addEndOfLine(
1255             "/COMPILATION_UNIT/CLASS_DEF"
1256                     + "[./IDENT[@text='InputMainSuppressionsStringPrinter']]/OBJBLOCK"
1257                     + "/METHOD_DEF[./IDENT[@text='getName']]"
1258                     + "/SLIST/VARIABLE_DEF[./IDENT[@text='var']]",
1259                 "/COMPILATION_UNIT/CLASS_DEF"
1260                     + "[./IDENT[@text='InputMainSuppressionsStringPrinter']]/OBJBLOCK"
1261                     + "/METHOD_DEF[./IDENT[@text='getName']]/SLIST"
1262                     + "/VARIABLE_DEF[./IDENT[@text='var']]/MODIFIERS",
1263                 "/COMPILATION_UNIT/CLASS_DEF"
1264                     + "[./IDENT[@text='InputMainSuppressionsStringPrinter']]/OBJBLOCK"
1265                     + "/METHOD_DEF[./IDENT[@text='getName']]/SLIST"
1266                     + "/VARIABLE_DEF[./IDENT[@text='var']]/TYPE",
1267                 "/COMPILATION_UNIT/CLASS_DEF"
1268                     + "[./IDENT[@text='InputMainSuppressionsStringPrinter']]/OBJBLOCK"
1269                     + "/METHOD_DEF[./IDENT[@text='getName']]/SLIST"
1270                     + "/VARIABLE_DEF[./IDENT[@text='var']]/TYPE/LITERAL_INT");
1271 
1272         assertMainReturnCode(0, getPath("InputMainSuppressionsStringPrinter.java"),
1273                 "-s", "7:9", "--tabWidth", "2");
1274         assertWithMessage("Unexpected output log")
1275             .that(systemOut.getCapturedData())
1276             .isEqualTo(expected);
1277         assertWithMessage("Unexpected system error log")
1278             .that(systemErr.getCapturedData())
1279             .isEqualTo("");
1280     }
1281 
1282     @Test
1283     public void testPrintSuppressionConflictingOptionsTvsC(@SysErr Capturable systemErr,
1284             @SysOut Capturable systemOut) {
1285         assertMainReturnCode(-1, "-c", "/google_checks.xml", getPath(""), "-s", "2:4");
1286         assertWithMessage("Unexpected output log")
1287             .that(systemOut.getCapturedData())
1288             .isEqualTo("Option '-s' cannot be used with other options."
1289                 + System.lineSeparator());
1290         assertWithMessage("Unexpected system error log")
1291             .that(systemErr.getCapturedData())
1292             .isEqualTo("");
1293     }
1294 
1295     @Test
1296     public void testPrintSuppressionConflictingOptionsTvsP(@SysErr Capturable systemErr,
1297             @SysOut Capturable systemOut) {
1298         assertMainReturnCode(-1, "-p", getPath("InputMainMycheckstyle.properties"), "-s", "2:4",
1299                 getPath(""));
1300         assertWithMessage("Unexpected output log")
1301             .that(systemOut.getCapturedData())
1302             .isEqualTo("Option '-s' cannot be used with other options."
1303                 + System.lineSeparator());
1304         assertWithMessage("Unexpected system error log")
1305             .that(systemErr.getCapturedData())
1306             .isEqualTo("");
1307     }
1308 
1309     @Test
1310     public void testPrintSuppressionConflictingOptionsTvsF(@SysErr Capturable systemErr,
1311             @SysOut Capturable systemOut) {
1312         assertMainReturnCode(-1, "-f", "plain", "-s", "2:4", getPath(""));
1313         assertWithMessage("Unexpected output log")
1314             .that(systemOut.getCapturedData())
1315             .isEqualTo("Option '-s' cannot be used with other options."
1316                 + System.lineSeparator());
1317         assertWithMessage("Unexpected system error log")
1318             .that(systemErr.getCapturedData())
1319             .isEqualTo("");
1320     }
1321 
1322     @Test
1323     public void testPrintSuppressionConflictingOptionsTvsO(@SysErr Capturable systemErr,
1324             @SysOut Capturable systemOut) throws IOException {
1325         final String outputPath = new File(temporaryFolder, "file.output").getCanonicalPath();
1326 
1327         assertMainReturnCode(-1, "-o", outputPath, "-s", "2:4", getPath(""));
1328         assertWithMessage("Unexpected output log")
1329             .that(systemOut.getCapturedData())
1330             .isEqualTo("Option '-s' cannot be used with other options."
1331                 + System.lineSeparator());
1332         assertWithMessage("Unexpected system error log")
1333             .that(systemErr.getCapturedData())
1334             .isEqualTo("");
1335     }
1336 
1337     @Test
1338     public void testPrintSuppressionOnMoreThanOneFile(@SysErr Capturable systemErr,
1339             @SysOut Capturable systemOut) {
1340         assertMainReturnCode(-1, "-s", "2:4", getPath(""), getPath(""));
1341         assertWithMessage("Unexpected output log")
1342             .that(systemOut.getCapturedData())
1343             .isEqualTo("Printing xpath suppressions is allowed for only one file."
1344                 + System.lineSeparator());
1345         assertWithMessage("Unexpected system error log")
1346             .that(systemErr.getCapturedData())
1347             .isEqualTo("");
1348     }
1349 
1350     @Test
1351     public void testGenerateXpathSuppressionOptionOne(@SysErr Capturable systemErr,
1352             @SysOut Capturable systemOut) {
1353         final String expected = addEndOfLine(
1354             "<?xml version=\"1.0\" encoding=\"UTF-8\"?>",
1355                 "<!DOCTYPE suppressions PUBLIC",
1356                 "    \"-//Checkstyle//DTD SuppressionXpathFilter Experimental Configuration 1.2"
1357                     + "//EN\"",
1358                 "    \"https://checkstyle.org/dtds/suppressions_1_2_xpath_experimental.dtd\">",
1359                 "<suppressions>",
1360                 "  <suppress-xpath",
1361                 "       files=\"InputMainComplexityOverflow.java\"",
1362                 "       checks=\"MissingJavadocMethodCheck\"",
1363                 "       query=\"/COMPILATION_UNIT/CLASS_DEF"
1364                     + "[./IDENT[@text='InputMainComplexityOverflow']]/OBJBLOCK"
1365                     + "/METHOD_DEF[./IDENT[@text='provokeNpathIntegerOverflow']]\"/>",
1366                 "  <suppress-xpath",
1367                 "       files=\"InputMainComplexityOverflow.java\"",
1368                 "       id=\"LeftCurlyEol\"",
1369                 "       query=\"/COMPILATION_UNIT/CLASS_DEF"
1370                     + "[./IDENT[@text='InputMainComplexityOverflow']]/OBJBLOCK"
1371                     + "/METHOD_DEF[./IDENT[@text='provokeNpathIntegerOverflow']]/SLIST\"/>",
1372                 "</suppressions>");
1373 
1374         assertMainReturnCode(0, "-c", "/google_checks.xml", "--generate-xpath-suppression",
1375                 getPath("InputMainComplexityOverflow.java"));
1376         assertWithMessage("Unexpected output log")
1377             .that(systemOut.getCapturedData())
1378             .isEqualTo(expected);
1379         assertWithMessage("Unexpected system error log")
1380             .that(systemErr.getCapturedData())
1381             .isEqualTo("");
1382     }
1383 
1384     @Test
1385     public void testGenerateXpathSuppressionOptionTwo(@SysErr Capturable systemErr,
1386             @SysOut Capturable systemOut) {
1387         final String expected = addEndOfLine(
1388             "<?xml version=\"1.0\" encoding=\"UTF-8\"?>",
1389             "<!DOCTYPE suppressions PUBLIC",
1390             "    \"-//Checkstyle//DTD SuppressionXpathFilter Experimental Configuration 1.2"
1391                 + "//EN\"",
1392             "    \"https://checkstyle.org/dtds/suppressions_1_2_xpath_experimental.dtd\">",
1393             "<suppressions>",
1394             "  <suppress-xpath",
1395             "       files=\"InputMainGenerateXpathSuppressions.java\"",
1396             "       checks=\"ExplicitInitializationCheck\"",
1397             "       query=\"/COMPILATION_UNIT/CLASS_DEF"
1398                 + "[./IDENT[@text='InputMainGenerateXpathSuppressions']]"
1399                 + "/OBJBLOCK/VARIABLE_DEF/IDENT[@text='low']\"/>",
1400             "  <suppress-xpath",
1401             "       files=\"InputMainGenerateXpathSuppressions.java\"",
1402             "       checks=\"IllegalThrowsCheck\"",
1403             "       query=\"/COMPILATION_UNIT/CLASS_DEF"
1404                 + "[./IDENT[@text='InputMainGenerateXpathSuppressions']]"
1405                 + "/OBJBLOCK/METHOD_DEF[./IDENT[@text='test']]/LITERAL_THROWS"
1406                 + "/IDENT[@text='RuntimeException']\"/>",
1407             "  <suppress-xpath",
1408             "       files=\"InputMainGenerateXpathSuppressions.java\"",
1409             "       checks=\"NestedForDepthCheck\"",
1410             "       query=\"/COMPILATION_UNIT/CLASS_DEF"
1411                 + "[./IDENT[@text='InputMainGenerateXpathSuppressions']]"
1412                 + "/OBJBLOCK/METHOD_DEF[./IDENT[@text='test']]/SLIST/LITERAL_FOR/SLIST"
1413                 + "/LITERAL_FOR/SLIST/LITERAL_FOR\"/>",
1414             "</suppressions>");
1415 
1416         assertMainReturnCode(0, "-c", getPath("InputMainConfig-xpath-suppressions.xml"),
1417                 "--generate-xpath-suppression",
1418                 getPath("InputMainGenerateXpathSuppressions.java"));
1419         assertWithMessage("Unexpected output log")
1420             .that(systemOut.getCapturedData())
1421             .isEqualTo(expected);
1422         assertWithMessage("Unexpected system error log")
1423             .that(systemErr.getCapturedData())
1424             .isEqualTo("");
1425     }
1426 
1427     @Test
1428     public void testGenerateXpathSuppressionOptionEmptyConfig(@SysErr Capturable systemErr,
1429             @SysOut Capturable systemOut) {
1430         final String expected = "";
1431 
1432         assertMainReturnCode(0, "-c", getPath("InputMainConfig-empty.xml"),
1433                 "--generate-xpath-suppression", getPath("InputMainComplexityOverflow.java"));
1434         assertWithMessage("Unexpected output log")
1435             .that(systemOut.getCapturedData())
1436             .isEqualTo(expected);
1437         assertWithMessage("Unexpected system error log")
1438             .that(systemErr.getCapturedData())
1439             .isEqualTo("");
1440     }
1441 
1442     @Test
1443     public void testGenerateXpathSuppressionOptionCustomOutput(@SysErr Capturable systemErr)
1444             throws IOException {
1445         final String expected = addEndOfLine(
1446             "<?xml version=\"1.0\" encoding=\"UTF-8\"?>",
1447                 "<!DOCTYPE suppressions PUBLIC",
1448                 "    \"-//Checkstyle//DTD SuppressionXpathFilter Experimental Configuration 1.2"
1449                     + "//EN\"",
1450                 "    \"https://checkstyle.org/dtds/suppressions_1_2_xpath_experimental.dtd\">",
1451                 "<suppressions>",
1452                 "  <suppress-xpath",
1453                 "       files=\"InputMainGenerateXpathSuppressionsTabWidth.java\"",
1454                 "       checks=\"ExplicitInitializationCheck\"",
1455                 "       query=\"/COMPILATION_UNIT/CLASS_DEF[./IDENT["
1456                     + "@text='InputMainGenerateXpathSuppressionsTabWidth']]"
1457                     + "/OBJBLOCK/VARIABLE_DEF/IDENT[@text='low']\"/>",
1458                 "</suppressions>");
1459         final File file = new File(temporaryFolder, "file.output");
1460         assertMainReturnCode(0, "-c", getPath("InputMainConfig-xpath-suppressions.xml"), "-o",
1461                 file.getPath(), "--generate-xpath-suppression",
1462                 getPath("InputMainGenerateXpathSuppressionsTabWidth.java"));
1463         try (BufferedReader br = Files.newBufferedReader(file.toPath())) {
1464             final String fileContent = br.lines().collect(Collectors.joining(EOL, "", EOL));
1465             assertWithMessage("Unexpected output log")
1466                 .that(fileContent)
1467                 .isEqualTo(expected);
1468             assertWithMessage("Unexpected system error log")
1469                 .that(systemErr.getCapturedData())
1470                 .isEqualTo("");
1471         }
1472     }
1473 
1474     @Test
1475     public void testGenerateXpathSuppressionOptionDefaultTabWidth(@SysErr Capturable systemErr,
1476             @SysOut Capturable systemOut) {
1477         final String expected = addEndOfLine(
1478             "<?xml version=\"1.0\" encoding=\"UTF-8\"?>",
1479                 "<!DOCTYPE suppressions PUBLIC",
1480                 "    \"-//Checkstyle//DTD SuppressionXpathFilter Experimental Configuration 1.2"
1481                     + "//EN\"",
1482                 "    \"https://checkstyle.org/dtds/suppressions_1_2_xpath_experimental.dtd\">",
1483                 "<suppressions>",
1484                 "  <suppress-xpath",
1485                 "       files=\"InputMainGenerateXpathSuppressionsTabWidth.java\"",
1486                 "       checks=\"ExplicitInitializationCheck\"",
1487                 "       query=\"/COMPILATION_UNIT/CLASS_DEF[./IDENT["
1488                     + "@text='InputMainGenerateXpathSuppressionsTabWidth']]"
1489                     + "/OBJBLOCK/VARIABLE_DEF/IDENT[@text='low']\"/>",
1490                 "</suppressions>");
1491 
1492         assertMainReturnCode(0, "-c", getPath("InputMainConfig-xpath-suppressions.xml"),
1493                 "--generate-xpath-suppression",
1494                 getPath("InputMainGenerateXpathSuppressionsTabWidth.java"));
1495         assertWithMessage("Unexpected output log")
1496             .that(systemOut.getCapturedData())
1497             .isEqualTo(expected);
1498         assertWithMessage("Unexpected system error log")
1499             .that(systemErr.getCapturedData())
1500             .isEqualTo("");
1501     }
1502 
1503     @Test
1504     public void testGenerateXpathSuppressionOptionCustomTabWidth(@SysErr Capturable systemErr,
1505             @SysOut Capturable systemOut) {
1506         final String expected = "";
1507 
1508         assertMainReturnCode(0, "-c", getPath("InputMainConfig-xpath-suppressions.xml"),
1509                 "--generate-xpath-suppression", "--tabWidth", "20",
1510                 getPath("InputMainGenerateXpathSuppressionsTabWidth.java"));
1511         assertWithMessage("Unexpected output log")
1512             .that(systemOut.getCapturedData())
1513             .isEqualTo(expected);
1514         assertWithMessage("Unexpected system error log")
1515             .that(systemErr.getCapturedData())
1516             .isEqualTo("");
1517     }
1518 
1519     @Test
1520     public void testGenerateChecksAndFilesSuppressionOptionOne(@SysErr Capturable systemErr,
1521             @SysOut Capturable systemOut) {
1522         final String expected = addEndOfLine(
1523             "<?xml version=\"1.0\" encoding=\"UTF-8\"?>",
1524                 "<!DOCTYPE suppressions PUBLIC",
1525                 "    \"-//Checkstyle//DTD SuppressionFilter Configuration 1.2//EN\"",
1526                 "    \"https://checkstyle.org/dtds/suppressions_1_2.dtd\">",
1527                 "<suppressions>",
1528                 "  <suppress",
1529                 "      files=\"InputMainComplexityOverflow.java\"",
1530                 "      checks=\"MissingJavadocMethodCheck\"/>",
1531                 "  <suppress",
1532                 "      files=\"InputMainComplexityOverflow.java\"",
1533                 "      id=\"LeftCurlyEol\"/>",
1534                 "</suppressions>");
1535 
1536         assertMainReturnCode(0, "-c", "/google_checks.xml",
1537                 "--generate-checks-and-files-suppression",
1538                 getPath("InputMainComplexityOverflow.java"));
1539         assertWithMessage("Unexpected output log")
1540             .that(systemOut.getCapturedData())
1541             .isEqualTo(expected);
1542         assertWithMessage("Unexpected system error log")
1543             .that(systemErr.getCapturedData())
1544             .isEqualTo("");
1545     }
1546 
1547     @Test
1548     public void testGenerateChecksAndFilesSuppressionOptionTwo(@SysErr Capturable systemErr,
1549             @SysOut Capturable systemOut) {
1550         final String expected = addEndOfLine(
1551             "<?xml version=\"1.0\" encoding=\"UTF-8\"?>",
1552             "<!DOCTYPE suppressions PUBLIC",
1553             "    \"-//Checkstyle//DTD SuppressionFilter Configuration 1.2//EN\"",
1554             "    \"https://checkstyle.org/dtds/suppressions_1_2.dtd\">",
1555             "<suppressions>",
1556             "  <suppress",
1557             "      files=\"InputMainGenerateChecksAndFilesSuppressions.java\"",
1558             "      id=\"InitializeViolation\"/>",
1559             "  <suppress",
1560             "      files=\"InputMainGenerateChecksAndFilesSuppressions.java\"",
1561             "      checks=\"IllegalThrowsCheck\"/>",
1562             "  <suppress",
1563             "      files=\"InputMainGenerateChecksAndFilesSuppressions.java\"",
1564             "      checks=\"NestedForDepthCheck\"/>",
1565             "  <suppress",
1566             "      files=\"InputMainGenerateChecksAndFilesSuppressions.java\"",
1567             "      id=\"MethodNaming\"/>",
1568             "</suppressions>");
1569 
1570         assertMainReturnCode(0, "-c", getPath("InputMainConfig-Checks-And-Files-suppressions.xml"),
1571                 "--generate-checks-and-files-suppression",
1572                 getPath("InputMainGenerateChecksAndFilesSuppressions.java"));
1573         assertWithMessage("Unexpected output log")
1574             .that(systemOut.getCapturedData())
1575             .isEqualTo(expected);
1576         assertWithMessage("Unexpected system error log")
1577             .that(systemErr.getCapturedData())
1578             .isEqualTo("");
1579     }
1580 
1581     @Test
1582     public void testGenerateChecksAndFilesSuppressionOptionEmptyConfig(@SysErr Capturable systemErr,
1583             @SysOut Capturable systemOut) {
1584         final String expected = "";
1585 
1586         assertMainReturnCode(0, "-c", getPath("InputMainConfig-empty.xml"),
1587                 "--generate-checks-and-files-suppression",
1588                 getPath("InputMainComplexityOverflow.java"));
1589         assertWithMessage("Unexpected output log")
1590             .that(systemOut.getCapturedData())
1591             .isEqualTo(expected);
1592         assertWithMessage("Unexpected system error log")
1593             .that(systemErr.getCapturedData())
1594             .isEqualTo("");
1595     }
1596 
1597     @Test
1598     public void testGenerateChecksAndFilesSuppressionOptionCustomOutput(
1599             @SysErr Capturable systemErr) throws IOException {
1600         final String expected = addEndOfLine(
1601             "<?xml version=\"1.0\" encoding=\"UTF-8\"?>",
1602                 "<!DOCTYPE suppressions PUBLIC",
1603                 "    \"-//Checkstyle//DTD SuppressionFilter Configuration 1.2//EN\"",
1604                 "    \"https://checkstyle.org/dtds/suppressions_1_2.dtd\">",
1605                 "<suppressions>",
1606                 "  <suppress",
1607                 "      files=\"InputMainGenerateChecksAndFilesSuppressionsTabWidth.java\"",
1608                 "      id=\"InitializeViolation\"/>",
1609                 "</suppressions>");
1610         final File file = new File(temporaryFolder, "file.output");
1611         assertMainReturnCode(0, "-c", getPath("InputMainConfig-Checks-And-Files-suppressions.xml"),
1612                 "-o", file.getPath(), "--generate-checks-and-files-suppression",
1613                 getPath("InputMainGenerateChecksAndFilesSuppressionsTabWidth.java"));
1614         try (BufferedReader br = Files.newBufferedReader(file.toPath())) {
1615             final String fileContent = br.lines().collect(Collectors.joining(EOL, "", EOL));
1616             assertWithMessage("Unexpected output log")
1617                 .that(fileContent)
1618                 .isEqualTo(expected);
1619             assertWithMessage("Unexpected system error log")
1620                 .that(systemErr.getCapturedData())
1621                 .isEqualTo("");
1622         }
1623     }
1624 
1625     @Test
1626     public void testGenerateChecksAndFilesSuppressionOptionDefaultTabWidth(
1627             @SysErr Capturable systemErr, @SysOut Capturable systemOut) {
1628         final String expected = addEndOfLine(
1629             "<?xml version=\"1.0\" encoding=\"UTF-8\"?>",
1630                 "<!DOCTYPE suppressions PUBLIC",
1631                 "    \"-//Checkstyle//DTD SuppressionFilter Configuration 1.2//EN\"",
1632                 "    \"https://checkstyle.org/dtds/suppressions_1_2.dtd\">",
1633                 "<suppressions>",
1634                 "  <suppress",
1635                 "      files=\"InputMainGenerateChecksAndFilesSuppressionsTabWidth.java\"",
1636                 "      id=\"InitializeViolation\"/>",
1637                 "</suppressions>");
1638 
1639         assertMainReturnCode(0, "-c", getPath("InputMainConfig-Checks-And-Files-suppressions.xml"),
1640                 "--generate-checks-and-files-suppression",
1641                 getPath("InputMainGenerateChecksAndFilesSuppressionsTabWidth.java"));
1642         assertWithMessage("Unexpected output log")
1643             .that(systemOut.getCapturedData())
1644             .isEqualTo(expected);
1645         assertWithMessage("Unexpected system error log")
1646             .that(systemErr.getCapturedData())
1647             .isEqualTo("");
1648     }
1649 
1650     @Test
1651     public void testGenerateChecksAndFilesSuppressionOptionCustomTabWidth(
1652             @SysErr Capturable systemErr, @SysOut Capturable systemOut) {
1653         final String expected = addEndOfLine(
1654             "<?xml version=\"1.0\" encoding=\"UTF-8\"?>",
1655                 "<!DOCTYPE suppressions PUBLIC",
1656                 "    \"-//Checkstyle//DTD SuppressionFilter Configuration 1.2//EN\"",
1657                 "    \"https://checkstyle.org/dtds/suppressions_1_2.dtd\">",
1658                 "<suppressions>",
1659                 "  <suppress",
1660                 "      files=\"InputMainGenerateChecksAndFilesSuppressionsTabWidth.java\"",
1661                 "      id=\"InitializeViolation\"/>",
1662                 "</suppressions>");
1663 
1664         assertMainReturnCode(0, "-c", getPath("InputMainConfig-Checks-And-Files-suppressions.xml"),
1665                 "--generate-checks-and-files-suppression", "--tabWidth", "20",
1666                 getPath("InputMainGenerateChecksAndFilesSuppressionsTabWidth.java"));
1667         assertWithMessage("Unexpected output log")
1668             .that(systemOut.getCapturedData())
1669             .isEqualTo(expected);
1670         assertWithMessage("Unexpected system error log")
1671             .that(systemErr.getCapturedData())
1672             .isEqualTo("");
1673     }
1674 
1675     /**
1676      * Verifies the output of the command line parameter "-J".
1677      *
1678      * @param systemErr wrapper for {@code System.err}
1679      * @param systemOut wrapper for {@code System.out}
1680      * @throws IOException if I/O exception occurs while reading the test input.
1681      * @noinspection RedundantThrows
1682      * @noinspectionreason RedundantThrows - false positive
1683      */
1684     @Test
1685     public void testPrintFullTreeOption(@SysErr Capturable systemErr, @SysOut Capturable systemOut)
1686             throws IOException {
1687         final String expected = Files.readString(Path.of(
1688             getPath("InputMainExpectedInputAstTreeStringPrinterJavadoc.txt")))
1689                 .replaceAll("\\\\r\\\\n", "\\\\n")
1690                 .replaceAll("\r\n", "\n");
1691 
1692         assertMainReturnCode(0, "-J", getPath("InputMainAstTreeStringPrinterJavadoc.java"));
1693         assertWithMessage("Unexpected output log")
1694             .that(systemOut.getCapturedData().replaceAll("\\\\r\\\\n", "\\\\n")
1695                         .replaceAll("\r\n", "\n"))
1696             .isEqualTo(expected);
1697         assertWithMessage("Unexpected system error log")
1698             .that(systemErr.getCapturedData())
1699             .isEqualTo("");
1700     }
1701 
1702     @Test
1703     public void testConflictingOptionsTvsC(@SysErr Capturable systemErr,
1704             @SysOut Capturable systemOut) {
1705         assertMainReturnCode(-1, "-c", "/google_checks.xml", "-t", getPath(""));
1706         assertWithMessage("Unexpected output log")
1707             .that(systemOut.getCapturedData())
1708             .isEqualTo("Option '-t' cannot be used with other options." + System.lineSeparator());
1709         assertWithMessage("Unexpected system error log")
1710             .that(systemErr.getCapturedData())
1711             .isEqualTo("");
1712     }
1713 
1714     @Test
1715     public void testConflictingOptionsTvsP(@SysErr Capturable systemErr,
1716             @SysOut Capturable systemOut) {
1717         assertMainReturnCode(-1, "-p", getPath("InputMainMycheckstyle.properties"), "-t",
1718                 getPath(""));
1719         assertWithMessage("Unexpected output log")
1720             .that(systemOut.getCapturedData())
1721             .isEqualTo("Option '-t' cannot be used with other options." + System.lineSeparator());
1722         assertWithMessage("Unexpected system error log")
1723             .that(systemErr.getCapturedData())
1724             .isEqualTo("");
1725     }
1726 
1727     @Test
1728     public void testConflictingOptionsTvsF(@SysErr Capturable systemErr,
1729             @SysOut Capturable systemOut) {
1730         assertMainReturnCode(-1, "-f", "plain", "-t", getPath(""));
1731         assertWithMessage("Unexpected output log")
1732             .that(systemOut.getCapturedData())
1733             .isEqualTo("Option '-t' cannot be used with other options." + System.lineSeparator());
1734         assertWithMessage("Unexpected system error log")
1735             .that(systemErr.getCapturedData())
1736             .isEqualTo("");
1737     }
1738 
1739     @Test
1740     public void testConflictingOptionsTvsS(@SysErr Capturable systemErr,
1741             @SysOut Capturable systemOut) throws IOException {
1742         final String outputPath = new File(temporaryFolder, "file.output").getCanonicalPath();
1743 
1744         assertMainReturnCode(-1, "-s", outputPath, "-t", getPath(""));
1745         assertWithMessage("Unexpected output log")
1746             .that(systemOut.getCapturedData())
1747             .isEqualTo("Option '-t' cannot be used with other options." + System.lineSeparator());
1748         assertWithMessage("Unexpected system error log")
1749             .that(systemErr.getCapturedData())
1750             .isEqualTo("");
1751     }
1752 
1753     @Test
1754     public void testConflictingOptionsTvsO(@SysErr Capturable systemErr,
1755             @SysOut Capturable systemOut) throws IOException {
1756         final String outputPath = new File(temporaryFolder, "file.output").getCanonicalPath();
1757 
1758         assertMainReturnCode(-1, "-o", outputPath, "-t", getPath(""));
1759         assertWithMessage("Unexpected output log")
1760             .that(systemOut.getCapturedData())
1761             .isEqualTo("Option '-t' cannot be used with other options." + System.lineSeparator());
1762         assertWithMessage("Unexpected system error log")
1763             .that(systemErr.getCapturedData())
1764             .isEqualTo("");
1765     }
1766 
1767     @Test
1768     public void testDebugOption(@SysErr Capturable systemErr, @SysOut Capturable systemOut) {
1769         assertMainReturnCode(0, "-c", "/google_checks.xml", getPath("InputMain.java"), "-d");
1770         assertWithMessage("Unexpected system error log")
1771             .that(systemErr.getCapturedData())
1772             .contains("FINE: Checkstyle debug logging enabled");
1773         assertWithMessage("Unexpected system error log")
1774             .that(systemOut.getCapturedData())
1775             .contains("Audit done.");
1776 
1777     }
1778 
1779     @Test
1780     public void testExcludeOption(@SysErr Capturable systemErr, @SysOut Capturable systemOut)
1781             throws IOException {
1782         final String filePath = getFilePath("");
1783         assertMainReturnCode(-1, "-c", "/google_checks.xml", filePath, "-e", filePath);
1784         assertWithMessage("Unexpected output log")
1785             .that(systemOut.getCapturedData())
1786             .isEqualTo("Files to process must be specified, found 0." + System.lineSeparator());
1787         assertWithMessage("Unexpected system error log")
1788             .that(systemErr.getCapturedData())
1789             .isEqualTo("");
1790     }
1791 
1792     @Test
1793     public void testExcludeOptionFile(@SysErr Capturable systemErr, @SysOut Capturable systemOut)
1794             throws IOException {
1795         final String filePath = getFilePath("InputMain.java");
1796         assertMainReturnCode(-1, "-c", "/google_checks.xml", filePath, "-e", filePath);
1797         assertWithMessage("Unexpected output log")
1798             .that(systemOut.getCapturedData())
1799             .isEqualTo("Files to process must be specified, found 0." + System.lineSeparator());
1800         assertWithMessage("Unexpected system error log")
1801             .that(systemErr.getCapturedData())
1802             .isEqualTo("");
1803     }
1804 
1805     @Test
1806     public void testExcludeRegexpOption(@SysErr Capturable systemErr, @SysOut Capturable systemOut)
1807             throws IOException {
1808         final String filePath = getFilePath("");
1809         assertMainReturnCode(-1, "-c", "/google_checks.xml", filePath, "-x", ".");
1810         assertWithMessage("Unexpected output log")
1811             .that(systemOut.getCapturedData())
1812             .isEqualTo("Files to process must be specified, found 0." + System.lineSeparator());
1813         assertWithMessage("Unexpected output log")
1814             .that(systemErr.getCapturedData())
1815             .isEqualTo("");
1816     }
1817 
1818     @Test
1819     public void testExcludeRegexpOptionFile(@SysErr Capturable systemErr,
1820             @SysOut Capturable systemOut) throws IOException {
1821         final String filePath = getFilePath("InputMain.java");
1822         assertMainReturnCode(-1, "-c", "/google_checks.xml", filePath, "-x", ".");
1823         assertWithMessage("Unexpected output log")
1824             .that(systemOut.getCapturedData())
1825             .isEqualTo("Files to process must be specified, found 0." + System.lineSeparator());
1826         assertWithMessage("Unexpected output log")
1827             .that(systemErr.getCapturedData())
1828             .isEqualTo("");
1829     }
1830 
1831     @Test
1832     public void testExcludeDirectoryNotMatch() throws Exception {
1833         final Class<?> optionsClass = Class.forName(Main.class.getName());
1834         final List<Pattern> list = new ArrayList<>();
1835         list.add(Pattern.compile("BAD_PATH"));
1836 
1837         final List<File> result = TestUtil.invokeStaticMethod(
1838                 optionsClass, "listFiles", new File(getFilePath("")), list);
1839         assertWithMessage("Invalid result size")
1840             .that(result)
1841             .isNotEmpty();
1842     }
1843 
1844     @Test
1845     public void testCustomRootModule(@SysErr Capturable systemErr, @SysOut Capturable systemOut) {
1846         TestRootModuleChecker.reset();
1847 
1848         assertMainReturnCode(0, "-c", getPath("InputMainConfig-custom-root-module.xml"),
1849                 getPath("InputMain.java"));
1850         assertWithMessage("Unexpected output log")
1851             .that(systemOut.getCapturedData())
1852             .isEqualTo("");
1853         assertWithMessage("Unexpected system error log")
1854             .that(systemErr.getCapturedData())
1855             .isEqualTo("");
1856         assertWithMessage("Invalid Checker state")
1857                 .that(TestRootModuleChecker.isProcessed())
1858                 .isTrue();
1859         assertWithMessage("RootModule should be destroyed")
1860                 .that(TestRootModuleChecker.isDestroyed())
1861                 .isTrue();
1862     }
1863 
1864     @Test
1865     public void testCustomSimpleRootModule(@SysErr Capturable systemErr) {
1866         TestRootModuleChecker.reset();
1867         assertMainReturnCode(-2, "-c", getPath("InputMainConfig-custom-simple-root-module.xml"),
1868                 getPath("InputMain.java"));
1869         final String checkstylePackage = "com.puppycrawl.tools.checkstyle.";
1870         final LocalizedMessage unableToInstantiateExceptionMessage = new LocalizedMessage(
1871                 Definitions.CHECKSTYLE_BUNDLE,
1872                 getClass(),
1873                 "PackageObjectFactory.unableToInstantiateExceptionMessage",
1874                 "TestRootModuleChecker",
1875                 checkstylePackage
1876                         + "TestRootModuleChecker, "
1877                         + "TestRootModuleCheckerCheck, " + checkstylePackage
1878                         + "TestRootModuleCheckerCheck");
1879         assertWithMessage(
1880                 "Unexpected system error log")
1881                         .that(systemErr.getCapturedData())
1882                         .startsWith(checkstylePackage + "api.CheckstyleException: "
1883                                 + unableToInstantiateExceptionMessage.getMessage());
1884         assertWithMessage("Invalid checker state")
1885                 .that(TestRootModuleChecker.isProcessed())
1886                 .isFalse();
1887     }
1888 
1889     @Test
1890     public void testExceptionOnExecuteIgnoredModuleWithUnknownModuleName(
1891             @SysErr Capturable systemErr) {
1892         assertMainReturnCode(-2, "-c", getPath("InputMainConfig-non-existent-classname-ignore.xml"),
1893                     "--executeIgnoredModules", getPath("InputMain.java"));
1894         final String cause = "com.puppycrawl.tools.checkstyle.api.CheckstyleException:"
1895                 + " cannot initialize module TreeWalker - ";
1896         assertWithMessage("Unexpected system error log")
1897                 .that(systemErr.getCapturedData())
1898                 .startsWith(cause);
1899     }
1900 
1901     @Test
1902     public void testExceptionOnExecuteIgnoredModuleWithBadPropertyValue(
1903             @SysErr Capturable systemErr) {
1904         assertMainReturnCode(-2, "-c", getPath("InputMainConfig-TypeName-bad-value.xml"),
1905                     "--executeIgnoredModules", getPath("InputMain.java"));
1906         final String cause = "com.puppycrawl.tools.checkstyle.api.CheckstyleException:"
1907                 + " cannot initialize module TreeWalker - ";
1908         final String causeDetail = "it is not a boolean";
1909         assertWithMessage("Unexpected system error log")
1910                 .that(systemErr.getCapturedData())
1911                 .startsWith(cause);
1912         assertWithMessage("Unexpected system error log")
1913                 .that(systemErr.getCapturedData())
1914                 .contains(causeDetail);
1915     }
1916 
1917     @Test
1918     public void testNoProblemOnExecuteIgnoredModuleWithBadPropertyValue(
1919             @SysErr Capturable systemErr) {
1920         assertMainReturnCode(0, "-c", getPath("InputMainConfig-TypeName-bad-value.xml"),
1921                     "", getPath("InputMain.java"));
1922         assertWithMessage("Unexpected system error log")
1923             .that(systemErr.getCapturedData())
1924                 .isEmpty();
1925     }
1926 
1927     @Test
1928     public void testMissingFiles(@SysErr Capturable systemErr, @SysOut Capturable systemOut) {
1929         assertMainReturnCode(-1);
1930         final String usage = "Missing required parameter: '<files or folders>'" + EOL + SHORT_USAGE;
1931         assertWithMessage("Unexpected output log")
1932             .that(systemOut.getCapturedData())
1933             .isEqualTo("");
1934         assertWithMessage("Unexpected system error log")
1935             .that(systemErr.getCapturedData())
1936             .isEqualTo(usage);
1937     }
1938 
1939     @Test
1940     public void testOutputFormatToStringLowercase() {
1941         assertWithMessage("expected xml")
1942             .that(Main.OutputFormat.XML.toString())
1943             .isEqualTo("xml");
1944         assertWithMessage("expected plain")
1945             .that(Main.OutputFormat.PLAIN.toString())
1946             .isEqualTo("plain");
1947     }
1948 
1949     @Test
1950     public void testXmlOutputFormatCreateListener() throws IOException {
1951         final ByteArrayOutputStream out = new ByteArrayOutputStream();
1952         final AuditListener listener = Main.OutputFormat.XML.createListener(out,
1953                 OutputStreamOptions.CLOSE);
1954         assertWithMessage("listener is XMLLogger")
1955                 .that(listener)
1956                 .isInstanceOf(XMLLogger.class);
1957     }
1958 
1959     @Test
1960     public void testSarifOutputFormatCreateListener() throws IOException {
1961         final ByteArrayOutputStream out = new ByteArrayOutputStream();
1962         final AuditListener listener = Main.OutputFormat.SARIF.createListener(out,
1963                 OutputStreamOptions.CLOSE);
1964         assertWithMessage("listener is SarifLogger")
1965                 .that(listener)
1966                 .isInstanceOf(SarifLogger.class);
1967     }
1968 
1969     @Test
1970     public void testPlainOutputFormatCreateListener() throws IOException {
1971         final ByteArrayOutputStream out = new ByteArrayOutputStream();
1972         final AuditListener listener = Main.OutputFormat.PLAIN.createListener(out,
1973                 OutputStreamOptions.CLOSE);
1974         assertWithMessage("listener is DefaultLogger")
1975                 .that(listener)
1976                 .isInstanceOf(DefaultLogger.class);
1977     }
1978 
1979     /**
1980      * Helper method to run {@link Main#main(String...)} and verify the exit code.
1981      * Uses {@link Mockito#mockStatic(Class)} to mock method {@link Runtime#exit(int)}
1982      * to avoid VM termination.
1983      *
1984      * @param expectedExitCode the expected exit code to verify
1985      * @param arguments the command line arguments
1986      * @noinspection CallToSystemExit, ResultOfMethodCallIgnored
1987      * @noinspectionreason CallToSystemExit - test helper method requires workaround to
1988      *      verify exit code
1989      * @noinspectionreason ResultOfMethodCallIgnored - Setup for mockito to only
1990      *                     mock getRuntime to avoid VM termination.
1991      */
1992     private static void assertMainReturnCode(int expectedExitCode, String... arguments) {
1993         final Runtime mock = mock();
1994         try (MockedStatic<Runtime> runtime = mockStatic(Runtime.class)) {
1995             runtime.when(Runtime::getRuntime)
1996                     .thenReturn(mock);
1997             Main.main(arguments);
1998         }
1999         catch (IOException exception) {
2000             assertWithMessage("Unexpected exception: %s", exception).fail();
2001         }
2002         verify(mock).exit(expectedExitCode);
2003     }
2004 
2005     /**
2006      * Print stream that shouldn't be closed. The purpose of this class is to ensure that
2007      * {@code System.out} and {@code System.err} are not closed by Checkstyle.
2008      */
2009     private static final class ShouldNotBeClosedStream extends PrintStream {
2010 
2011         private boolean isClosed;
2012 
2013         private ShouldNotBeClosedStream() {
2014             super(new ByteArrayOutputStream(), false, StandardCharsets.UTF_8);
2015         }
2016 
2017         @Override
2018         public void close() {
2019             isClosed = true;
2020             super.close();
2021         }
2022 
2023     }
2024 
2025 }