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