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