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