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