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