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.Checker.EXCEPTION_MSG;
24  import static com.puppycrawl.tools.checkstyle.DefaultLogger.AUDIT_FINISHED_MESSAGE;
25  import static com.puppycrawl.tools.checkstyle.DefaultLogger.AUDIT_STARTED_MESSAGE;
26  import static com.puppycrawl.tools.checkstyle.checks.NewlineAtEndOfFileCheck.MSG_KEY_NO_NEWLINE_EOF;
27  import static com.puppycrawl.tools.checkstyle.checks.sizes.LineLengthCheck.MSG_KEY;
28  import static com.puppycrawl.tools.checkstyle.internal.utils.TestUtil.getExpectedThrowable;
29  
30  import java.io.BufferedReader;
31  import java.io.BufferedWriter;
32  import java.io.ByteArrayInputStream;
33  import java.io.ByteArrayOutputStream;
34  import java.io.File;
35  import java.io.IOError;
36  import java.io.IOException;
37  import java.io.InputStream;
38  import java.io.InputStreamReader;
39  import java.io.LineNumberReader;
40  import java.io.OutputStream;
41  import java.io.Serial;
42  import java.io.UnsupportedEncodingException;
43  import java.nio.charset.StandardCharsets;
44  import java.nio.file.Files;
45  import java.util.ArrayList;
46  import java.util.Arrays;
47  import java.util.Collections;
48  import java.util.HashSet;
49  import java.util.List;
50  import java.util.Locale;
51  import java.util.Objects;
52  import java.util.Properties;
53  import java.util.Set;
54  import java.util.SortedSet;
55  import java.util.TreeSet;
56  import java.util.UUID;
57  
58  import org.apache.tools.ant.BuildException;
59  import org.apache.tools.ant.Project;
60  import org.junit.jupiter.api.Test;
61  import org.junit.jupiter.api.io.TempDir;
62  
63  import com.puppycrawl.tools.checkstyle.AbstractAutomaticBean.OutputStreamOptions;
64  import com.puppycrawl.tools.checkstyle.ant.CheckstyleAntTask;
65  import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
66  import com.puppycrawl.tools.checkstyle.api.AbstractFileSetCheck;
67  import com.puppycrawl.tools.checkstyle.api.AuditEvent;
68  import com.puppycrawl.tools.checkstyle.api.AuditListener;
69  import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
70  import com.puppycrawl.tools.checkstyle.api.Configuration;
71  import com.puppycrawl.tools.checkstyle.api.Context;
72  import com.puppycrawl.tools.checkstyle.api.DetailAST;
73  import com.puppycrawl.tools.checkstyle.api.ExternalResourceHolder;
74  import com.puppycrawl.tools.checkstyle.api.FileText;
75  import com.puppycrawl.tools.checkstyle.api.Filter;
76  import com.puppycrawl.tools.checkstyle.api.FilterSet;
77  import com.puppycrawl.tools.checkstyle.api.MessageDispatcher;
78  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
79  import com.puppycrawl.tools.checkstyle.api.Violation;
80  import com.puppycrawl.tools.checkstyle.checks.NewlineAtEndOfFileCheck;
81  import com.puppycrawl.tools.checkstyle.checks.TranslationCheck;
82  import com.puppycrawl.tools.checkstyle.checks.coding.HiddenFieldCheck;
83  import com.puppycrawl.tools.checkstyle.checks.sizes.LineLengthCheck;
84  import com.puppycrawl.tools.checkstyle.filefilters.BeforeExecutionExclusionFileFilter;
85  import com.puppycrawl.tools.checkstyle.filters.SuppressionFilter;
86  import com.puppycrawl.tools.checkstyle.internal.testmodules.CheckWhichThrowsError;
87  import com.puppycrawl.tools.checkstyle.internal.testmodules.DebugAuditAdapter;
88  import com.puppycrawl.tools.checkstyle.internal.testmodules.DebugFilter;
89  import com.puppycrawl.tools.checkstyle.internal.testmodules.TestBeforeExecutionFileFilter;
90  import com.puppycrawl.tools.checkstyle.internal.testmodules.TestFileSetCheck;
91  import com.puppycrawl.tools.checkstyle.internal.utils.CloseAndFlushTestByteArrayOutputStream;
92  import com.puppycrawl.tools.checkstyle.internal.utils.TestUtil;
93  import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
94  import de.thetaphi.forbiddenapis.SuppressForbidden;
95  
96  /**
97   * CheckerTest.
98   *
99   * @noinspection ClassWithTooManyDependencies
100  * @noinspectionreason ClassWithTooManyDependencies - complex tests require a large number
101  *      of imports
102  */
103 public class CheckerTest extends AbstractModuleTestSupport {
104 
105     @TempDir
106     public File temporaryFolder;
107 
108     private File createTempFile(String prefix) throws IOException {
109         return createTempFile(prefix, ".tmp");
110     }
111 
112     private File createTempFile(String prefix, String suffix) throws IOException {
113         final String name = Objects.requireNonNull(prefix)
114                 + UUID.randomUUID()
115                 + Objects.requireNonNull(suffix);
116         return Files.createFile(temporaryFolder.toPath().resolve(name)).toFile();
117     }
118 
119     @Override
120     public String getPackageLocation() {
121         return "com/puppycrawl/tools/checkstyle/checker";
122     }
123 
124     private String getLocalizedMessage(String messageKey, Object... args) {
125         final LocalizedMessage localizedMessage = new LocalizedMessage(
126             Definitions.CHECKSTYLE_BUNDLE, getClass(),
127                     messageKey, args);
128 
129         return localizedMessage.getMessage();
130     }
131 
132     @Test
133     public void testDestroy() throws Exception {
134         final Checker checker = new Checker();
135         final DebugAuditAdapter auditAdapter = new DebugAuditAdapter();
136         checker.addListener(auditAdapter);
137         final TestFileSetCheck fileSet = new TestFileSetCheck();
138         checker.addFileSetCheck(fileSet);
139         final DebugFilter filter = new DebugFilter();
140         checker.addFilter(filter);
141         final TestBeforeExecutionFileFilter fileFilter = new TestBeforeExecutionFileFilter();
142         checker.addBeforeExecutionFileFilter(fileFilter);
143 
144         // should remove all listeners, file sets, and filters
145         checker.destroy();
146 
147         final File tempFile = createTempFile("junit");
148         checker.process(Collections.singletonList(tempFile));
149         final SortedSet<Violation> violations = new TreeSet<>();
150         violations.add(new Violation(1, 0, "a Bundle", "message.key",
151                 new Object[] {"arg"}, null, getClass(), null));
152         checker.fireErrors("Some File Name", violations);
153 
154         assertWithMessage("Checker.destroy() doesn't remove listeners.")
155                 .that(auditAdapter.wasCalled())
156                 .isFalse();
157         assertWithMessage("Checker.destroy() doesn't remove file sets.")
158                 .that(fileSet.wasCalled())
159                 .isFalse();
160         assertWithMessage("Checker.destroy() doesn't remove filters.")
161                 .that(filter.wasCalled())
162                 .isFalse();
163         assertWithMessage("Checker.destroy() doesn't remove file filters.")
164                 .that(fileFilter.wasCalled())
165                 .isFalse();
166     }
167 
168     @Test
169     public void testAddListener() throws Exception {
170         final Checker checker = new Checker();
171         final DebugAuditAdapter auditAdapter = new DebugAuditAdapter();
172         checker.addListener(auditAdapter);
173 
174         checker.process(List.of());
175         assertWithMessage("Audit start event should identify the checker")
176                 .that(auditAdapter.getAuditStartedEvent().getSource())
177                 .isSameInstanceAs(checker);
178         assertWithMessage("Audit finish event should identify the checker")
179                 .that(auditAdapter.getAuditFinishedEvent().getSource())
180                 .isSameInstanceAs(checker);
181 
182         auditAdapter.resetListener();
183         checker.fireFileStarted("Some File Name");
184         assertWithMessage("Checker.fireFileStarted() doesn't call listener")
185                 .that(auditAdapter.wasCalled())
186                 .isTrue();
187         assertWithMessage("Checker.fireFileStarted() doesn't pass event")
188                 .that(auditAdapter.wasEventPassed())
189                 .isTrue();
190 
191         auditAdapter.resetListener();
192         checker.fireFileFinished("Some File Name");
193         assertWithMessage("Checker.fireFileFinished() doesn't call listener")
194                 .that(auditAdapter.wasCalled())
195                 .isTrue();
196         assertWithMessage("Checker.fireFileFinished() doesn't pass event")
197                 .that(auditAdapter.wasEventPassed())
198                 .isTrue();
199 
200         auditAdapter.resetListener();
201         final SortedSet<Violation> violations = new TreeSet<>();
202         violations.add(new Violation(1, 0, "a Bundle", "message.key",
203                 new Object[] {"arg"}, null, getClass(), null));
204         checker.fireErrors("Some File Name", violations);
205         assertWithMessage("Checker.fireErrors() doesn't call listener")
206                 .that(auditAdapter.wasCalled())
207                 .isTrue();
208         assertWithMessage("Checker.fireErrors() doesn't pass event")
209                 .that(auditAdapter.wasEventPassed())
210                 .isTrue();
211     }
212 
213     @Test
214     public void testRemoveListener() throws Exception {
215         final Checker checker = new Checker();
216         final DebugAuditAdapter auditAdapter = new DebugAuditAdapter();
217         final DebugAuditAdapter aa2 = new DebugAuditAdapter();
218         checker.addListener(auditAdapter);
219         checker.addListener(aa2);
220         checker.removeListener(auditAdapter);
221 
222         checker.process(List.of());
223         assertWithMessage("Remaining listener should receive the audit start event")
224                 .that(aa2.getAuditStartedEvent().getSource())
225                 .isSameInstanceAs(checker);
226         assertWithMessage("Removed listener should not receive the audit start event")
227                 .that(auditAdapter.getAuditStartedEvent())
228                 .isNull();
229         assertWithMessage("Remaining listener should receive the audit finish event")
230                 .that(aa2.getAuditFinishedEvent().getSource())
231                 .isSameInstanceAs(checker);
232         assertWithMessage("Removed listener should not receive the audit finish event")
233                 .that(auditAdapter.getAuditFinishedEvent())
234                 .isNull();
235 
236         aa2.resetListener();
237         checker.fireFileStarted("Some File Name");
238         assertWithMessage("Checker.fireFileStarted() doesn't call listener")
239                 .that(aa2.wasCalled())
240                 .isTrue();
241         assertWithMessage("Checker.fireFileStarted() does call removed listener")
242                 .that(auditAdapter.wasCalled())
243                 .isFalse();
244 
245         aa2.resetListener();
246         checker.fireFileFinished("Some File Name");
247         assertWithMessage("Checker.fireFileFinished() doesn't call listener")
248                 .that(aa2.wasCalled())
249                 .isTrue();
250         assertWithMessage("Checker.fireFileFinished() does call removed listener")
251                 .that(auditAdapter.wasCalled())
252                 .isFalse();
253 
254         aa2.resetListener();
255         final SortedSet<Violation> violations = new TreeSet<>();
256         violations.add(new Violation(1, 0, "a Bundle", "message.key",
257                 new Object[] {"arg"}, null, getClass(), null));
258         checker.fireErrors("Some File Name", violations);
259         assertWithMessage("Checker.fireErrors() doesn't call listener")
260                 .that(aa2.wasCalled())
261                 .isTrue();
262         assertWithMessage("Checker.fireErrors() does call removed listener")
263                 .that(auditAdapter.wasCalled())
264                 .isFalse();
265     }
266 
267     @Test
268     public void testAddBeforeExecutionFileFilter() throws Exception {
269         final Checker checker = new Checker();
270         final TestBeforeExecutionFileFilter filter = new TestBeforeExecutionFileFilter();
271 
272         checker.addBeforeExecutionFileFilter(filter);
273 
274         filter.resetFilter();
275         checker.process(Collections.singletonList(new File("dummy.java")));
276         assertWithMessage("Checker.acceptFileStarted() doesn't call filter")
277                 .that(filter.wasCalled())
278                 .isTrue();
279     }
280 
281     @Test
282     public void testRemoveBeforeExecutionFileFilter() throws Exception {
283         final Checker checker = new Checker();
284         final TestBeforeExecutionFileFilter filter = new TestBeforeExecutionFileFilter();
285         final TestBeforeExecutionFileFilter f2 = new TestBeforeExecutionFileFilter();
286         checker.addBeforeExecutionFileFilter(filter);
287         checker.addBeforeExecutionFileFilter(f2);
288         checker.removeBeforeExecutionFileFilter(filter);
289 
290         f2.resetFilter();
291         checker.process(Collections.singletonList(new File("dummy.java")));
292         assertWithMessage("Checker.acceptFileStarted() doesn't call filter")
293                 .that(f2.wasCalled())
294                 .isTrue();
295         assertWithMessage("Checker.acceptFileStarted() does call removed filter")
296                 .that(filter.wasCalled())
297                 .isFalse();
298     }
299 
300     @Test
301     public void testAddFilter() {
302         final Checker checker = new Checker();
303         final DebugFilter filter = new DebugFilter();
304 
305         checker.addFilter(filter);
306 
307         filter.resetFilter();
308         final SortedSet<Violation> violations = new TreeSet<>();
309         violations.add(new Violation(1, 0, "a Bundle", "message.key",
310                 new Object[] {"arg"}, null, getClass(), null));
311         checker.fireErrors("Some File Name", violations);
312         assertWithMessage("Checker.fireErrors() doesn't call filter")
313                 .that(filter.wasCalled())
314                 .isTrue();
315     }
316 
317     @Test
318     public void testRemoveFilter() {
319         final Checker checker = new Checker();
320         final DebugFilter filter = new DebugFilter();
321         final DebugFilter f2 = new DebugFilter();
322         checker.addFilter(filter);
323         checker.addFilter(f2);
324         checker.removeFilter(filter);
325 
326         f2.resetFilter();
327         final SortedSet<Violation> violations = new TreeSet<>();
328         violations.add(new Violation(1, 0, "a Bundle", "message.key",
329                 new Object[] {"arg"}, null, getClass(), null));
330         checker.fireErrors("Some File Name", violations);
331         assertWithMessage("Checker.fireErrors() doesn't call filter")
332                 .that(f2.wasCalled())
333                 .isTrue();
334         assertWithMessage("Checker.fireErrors() does call removed filter")
335                 .that(filter.wasCalled())
336                 .isFalse();
337     }
338 
339     @Test
340     public void testFileExtensions() throws Exception {
341         final DefaultConfiguration checkerConfig = new DefaultConfiguration("configuration");
342         checkerConfig.addProperty("charset", StandardCharsets.UTF_8.name());
343         checkerConfig.addProperty("cacheFile", createTempFile("junit").getPath());
344 
345         final Checker checker = new Checker();
346         checker.setModuleClassLoader(Thread.currentThread().getContextClassLoader());
347         checker.configure(checkerConfig);
348 
349         final DebugAuditAdapter auditAdapter = new DebugAuditAdapter();
350         checker.addListener(auditAdapter);
351 
352         final List<File> files = new ArrayList<>();
353         final File file = new File("file.pdf");
354         files.add(file);
355         final File otherFile = new File("file.java");
356         files.add(otherFile);
357         final String[] fileExtensions = {"java", "xml", "properties"};
358         checker.setFileExtensions(fileExtensions);
359         checker.setCacheFile(createTempFile("junit").getPath());
360         final int counter = checker.process(files);
361 
362         // comparing to 1 as there is only one legal file in input
363         final int numLegalFiles = 1;
364         final PropertyCacheFile cache = TestUtil.getInternalState(checker,
365                 "cacheFile", PropertyCacheFile.class);
366         assertWithMessage("There were more legal files than expected")
367             .that(counter)
368             .isEqualTo(numLegalFiles);
369         assertWithMessage("Audit was started on larger amount of files than expected")
370             .that(auditAdapter.getNumFilesStarted())
371             .isEqualTo(numLegalFiles);
372         assertWithMessage("Audit was finished on larger amount of files than expected")
373             .that(auditAdapter.getNumFilesFinished())
374             .isEqualTo(numLegalFiles);
375         assertWithMessage("Cache shout not contain any file")
376             .that(cache.get(new File("file.java").getCanonicalPath()))
377             .isNull();
378     }
379 
380     @Test
381     public void testIgnoredFileExtensions() throws Exception {
382         final DefaultConfiguration checkerConfig = new DefaultConfiguration("configuration");
383         checkerConfig.addProperty("charset", StandardCharsets.UTF_8.name());
384         final File tempFile = createTempFile("junit");
385         checkerConfig.addProperty("cacheFile", tempFile.getPath());
386 
387         final Checker checker = new Checker();
388         checker.setModuleClassLoader(Thread.currentThread().getContextClassLoader());
389         checker.configure(checkerConfig);
390 
391         final DebugAuditAdapter auditAdapter = new DebugAuditAdapter();
392         checker.addListener(auditAdapter);
393 
394         final List<File> allIgnoredFiles = new ArrayList<>();
395         final File ignoredFile = new File("file.pdf");
396         allIgnoredFiles.add(ignoredFile);
397         final String[] fileExtensions = {"java", "xml", "properties"};
398         checker.setFileExtensions(fileExtensions);
399         checker.setCacheFile(createTempFile("junit").getPath());
400         final int counter = checker.process(allIgnoredFiles);
401 
402         // comparing to 0 as there is no legal file in input
403         final int numLegalFiles = 0;
404         assertWithMessage("There were more legal files than expected")
405             .that(counter)
406             .isEqualTo(numLegalFiles);
407         assertWithMessage("Audit was started on larger amount of files than expected")
408             .that(auditAdapter.getNumFilesStarted())
409             .isEqualTo(numLegalFiles);
410         assertWithMessage("Audit was finished on larger amount of files than expected")
411             .that(auditAdapter.getNumFilesFinished())
412             .isEqualTo(numLegalFiles);
413     }
414 
415     @Test
416     public void testSetters() {
417         // all  that is set by reflection, so just make code coverage be happy
418         final Checker checker = new Checker();
419         checker.setBasedir("some");
420         checker.setSeverity("ignore");
421 
422         final PackageObjectFactory factory = new PackageObjectFactory(
423             new HashSet<>(), Thread.currentThread().getContextClassLoader());
424         checker.setModuleFactory(factory);
425 
426         checker.setFileExtensions((String[]) null);
427         checker.setFileExtensions(".java", "xml");
428 
429         final UnsupportedEncodingException exc =
430                 getExpectedThrowable(UnsupportedEncodingException.class, () -> {
431                     checker.setCharset("UNKNOWN-CHARSET");
432                 }, "Exception is expected");
433         assertWithMessage("Error message is not expected")
434             .that(exc.getMessage())
435             .isEqualTo("unsupported charset: 'UNKNOWN-CHARSET'");
436     }
437 
438     @Test
439     public void testSetSeverity() throws Exception {
440         final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
441 
442         verifyWithInlineXmlConfig(getPath("InputCheckerTestSeverity.java"), expected);
443     }
444 
445     @Test
446     public void testNoClassLoaderNoModuleFactory() {
447         final Checker checker = new Checker();
448 
449         final CheckstyleException exc =
450                 getExpectedThrowable(CheckstyleException.class,
451                     checker::finishLocalSetup, "Exception is expected");
452         assertWithMessage("Error message is not expected")
453             .that(exc.getMessage())
454             .isEqualTo("if no custom moduleFactory is set,"
455                 + " moduleClassLoader must be specified");
456     }
457 
458     @Test
459     public void testNoModuleFactory() throws Exception {
460         final Checker checker = new Checker();
461         final ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
462 
463         checker.setModuleClassLoader(classLoader);
464         checker.finishLocalSetup();
465         final Context actualCtx = TestUtil.getInternalState(checker, "childContext", Context.class);
466 
467         assertWithMessage("Default module factory should be created when it is not specified")
468             .that(actualCtx.get("moduleFactory"))
469             .isNotNull();
470     }
471 
472     @Test
473     public void testFinishLocalSetupFullyInitialized() throws Exception {
474         final Checker checker = new Checker();
475         final ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
476         checker.setModuleClassLoader(contextClassLoader);
477         final PackageObjectFactory factory = new PackageObjectFactory(
478             new HashSet<>(), contextClassLoader);
479         checker.setModuleFactory(factory);
480         checker.setBasedir("testBaseDir");
481         checker.setLocaleLanguage("it");
482         checker.setLocaleCountry("IT");
483         checker.finishLocalSetup();
484 
485         final Context context = TestUtil.getInternalState(checker, "childContext", Context.class);
486         final String encoding = StandardCharsets.UTF_8.name();
487         assertWithMessage("Charset was different than expected")
488             .that(context.get("charset"))
489             .isEqualTo(encoding);
490         assertWithMessage("Severity is set to unexpected value")
491             .that(context.get("severity"))
492             .isEqualTo("error");
493         assertWithMessage("Basedir is set to unexpected value")
494             .that(context.get("basedir"))
495             .isEqualTo("testBaseDir");
496 
497         final Locale locale = TestUtil.getInternalStaticState(LocalizedMessage.class,
498                 "messageLocale", Locale.class);
499         assertWithMessage("Locale is set to unexpected value")
500             .that(locale)
501             .isEqualTo(Locale.ITALY);
502     }
503 
504     @Test
505     public void testSetupChildExceptions() {
506         final Checker checker = new Checker();
507         final PackageObjectFactory factory = new PackageObjectFactory(
508             new HashSet<>(), Thread.currentThread().getContextClassLoader());
509         checker.setModuleFactory(factory);
510 
511         final Configuration config = new DefaultConfiguration("java.lang.String");
512         final CheckstyleException exc =
513                 getExpectedThrowable(CheckstyleException.class, () -> {
514                     checker.setupChild(config);
515                 }, "Exception is expected");
516         assertWithMessage("Error message is not expected")
517             .that(exc.getMessage())
518             .isEqualTo("java.lang.String is not allowed as a child in Checker");
519     }
520 
521     @Test
522     public void testSetupChildInvalidProperty() {
523         final DefaultConfiguration checkConfig = createModuleConfig(HiddenFieldCheck.class);
524         checkConfig.addProperty("$$No such property", null);
525         final CheckstyleException exc =
526                 getExpectedThrowable(CheckstyleException.class, () -> {
527                     createChecker(checkConfig);
528                 }, "Exception is expected");
529         assertWithMessage("Error message is not expected")
530             .that(exc.getMessage())
531             .isEqualTo("cannot initialize module com.puppycrawl.tools.checkstyle.TreeWalker"
532                     + " - cannot initialize module " + checkConfig.getName());
533     }
534 
535     @Test
536     public void testSetupChildListener() throws Exception {
537         final Checker checker = new Checker();
538         final PackageObjectFactory factory = new PackageObjectFactory(
539             new HashSet<>(), Thread.currentThread().getContextClassLoader());
540         checker.setModuleFactory(factory);
541 
542         final Configuration config = new DefaultConfiguration(
543             DebugAuditAdapter.class.getCanonicalName());
544         checker.setupChild(config);
545 
546         final List<AuditListener> listeners = TestUtil.getInternalStateListAuditListener(checker,
547                 "listeners");
548         assertWithMessage("Invalid child listener class")
549                 .that(listeners.getLast() instanceof DebugAuditAdapter)
550                 .isTrue();
551     }
552 
553     @Test
554     public void testDestroyCheckerWithWrongCacheFileNameLength() throws Exception {
555         final Checker checker = new Checker();
556         final PackageObjectFactory factory = new PackageObjectFactory(
557             new HashSet<>(), Thread.currentThread().getContextClassLoader());
558         checker.setModuleFactory(factory);
559         checker.configure(new DefaultConfiguration("default config"));
560         // We set wrong file name length in order to reproduce IOException on OS Linux, OS Windows.
561         // The maximum file name length which is allowed in most UNIX, Windows file systems is 255.
562         // See https://en.wikipedia.org/wiki/Filename;
563         checker.setCacheFile(String.format(Locale.ENGLISH, "%0300d", 0));
564         final IllegalStateException exc =
565                 getExpectedThrowable(IllegalStateException.class,
566                     checker::destroy, "Exception did not happen");
567         assertWithMessage("Cause of exception differs from IOException")
568             .that(exc.getCause())
569             .isInstanceOf(IOException.class);
570         assertWithMessage("Exception message differ")
571             .that(exc.getMessage())
572             .isEqualTo(getLocalizedMessage(
573                     "Checker.cacheFilesException"));
574     }
575 
576     /**
577      * It is OK to have long test method name here as it describes the test purpose.
578      */
579     @Test
580     public void testCacheAndCheckWhichDoesNotImplementExternalResourceHolderInterface()
581             throws Exception {
582         assertWithMessage("ExternalResourceHolder has changed his parent")
583                 .that(ExternalResourceHolder.class.isAssignableFrom(HiddenFieldCheck.class))
584                 .isFalse();
585         final DefaultConfiguration checkConfig = createModuleConfig(HiddenFieldCheck.class);
586 
587         final DefaultConfiguration treeWalkerConfig = createModuleConfig(TreeWalker.class);
588         treeWalkerConfig.addChild(checkConfig);
589 
590         final DefaultConfiguration checkerConfig = createRootConfig(treeWalkerConfig);
591         checkerConfig.addProperty("charset", StandardCharsets.UTF_8.name());
592 
593         final File cacheFile = createTempFile("junit");
594         checkerConfig.addProperty("cacheFile", cacheFile.getPath());
595 
596         final File tmpFile = createTempFile("file", ".java");
597 
598         execute(checkerConfig, tmpFile.getPath());
599         final Properties cacheAfterFirstRun = new Properties();
600         try (BufferedReader reader = Files.newBufferedReader(cacheFile.toPath())) {
601             cacheAfterFirstRun.load(reader);
602         }
603 
604         // one more time to reuse cache
605         execute(checkerConfig, tmpFile.getPath());
606         final Properties cacheAfterSecondRun = new Properties();
607         try (BufferedReader reader = Files.newBufferedReader(cacheFile.toPath())) {
608             cacheAfterSecondRun.load(reader);
609         }
610 
611         assertWithMessage("Cache from first run differs from second run cache")
612             .that(cacheAfterSecondRun)
613             .isEqualTo(cacheAfterFirstRun);
614     }
615 
616     @Test
617     public void testWithCacheWithNoViolation() throws Exception {
618         final Checker checker = new Checker();
619         final PackageObjectFactory factory = new PackageObjectFactory(
620             new HashSet<>(), Thread.currentThread().getContextClassLoader());
621         checker.setModuleFactory(factory);
622         checker.configure(createModuleConfig(TranslationCheck.class));
623 
624         final File cacheFile = createTempFile("junit");
625         checker.setCacheFile(cacheFile.getPath());
626 
627         checker.setupChild(createModuleConfig(TranslationCheck.class));
628         final File tmpFile = createTempFile("file", ".java");
629         final List<File> files = new ArrayList<>(1);
630         files.add(tmpFile);
631         checker.process(files);
632 
633         // invoke destroy to persist cache
634         checker.destroy();
635 
636         final Properties cache = new Properties();
637         try (BufferedReader reader = Files.newBufferedReader(cacheFile.toPath())) {
638             cache.load(reader);
639         }
640 
641         // There should 2 objects in cache: processed file (file.java) and checker configuration.
642         final int expectedNumberOfObjectsInCache = 2;
643         assertWithMessage("Cache has unexpected size")
644             .that(cache)
645             .hasSize(expectedNumberOfObjectsInCache);
646 
647         final String expectedConfigHash = "D581D4A2BD482D4E1EF1F82459356BA2D8A3B" + "FC3";
648         assertWithMessage("Cache has unexpected hash")
649             .that(cache.getProperty(PropertyCacheFile.CONFIG_HASH_KEY))
650             .isEqualTo(expectedConfigHash);
651 
652         assertWithMessage("Cache file has null path")
653             .that(cache.getProperty(tmpFile.getPath()))
654             .isNotNull();
655     }
656 
657     @Test
658     public void testClearExistingCache() throws Exception {
659         final DefaultConfiguration checkerConfig = createRootConfig(null);
660         checkerConfig.addProperty("charset", StandardCharsets.UTF_8.name());
661         final File cacheFile = createTempFile("junit");
662         checkerConfig.addProperty("cacheFile", cacheFile.getPath());
663 
664         final Checker checker = new Checker();
665         checker.setModuleClassLoader(Thread.currentThread().getContextClassLoader());
666         checker.configure(checkerConfig);
667         checker.addListener(getBriefUtLogger());
668 
669         checker.clearCache();
670         // invoke destroy to persist cache
671         checker.destroy();
672 
673         final Properties cacheAfterClear = new Properties();
674         try (BufferedReader reader = Files.newBufferedReader(cacheFile.toPath())) {
675             cacheAfterClear.load(reader);
676         }
677 
678         assertWithMessage("Cache has unexpected size")
679             .that(cacheAfterClear)
680             .hasSize(1);
681         assertWithMessage("Cache has null hash")
682             .that(cacheAfterClear.getProperty(PropertyCacheFile.CONFIG_HASH_KEY))
683             .isNotNull();
684 
685         final String pathToEmptyFile = createTempFile("file", ".java").getPath();
686 
687         // file that should be audited is not in cache
688         execute(checkerConfig, pathToEmptyFile);
689         final Properties cacheAfterSecondRun = new Properties();
690         try (BufferedReader reader = Files.newBufferedReader(cacheFile.toPath())) {
691             cacheAfterSecondRun.load(reader);
692         }
693 
694         assertWithMessage("Cache has null path")
695             .that(cacheAfterSecondRun.getProperty(pathToEmptyFile))
696             .isNotNull();
697         final String cacheHash = cacheAfterSecondRun.getProperty(PropertyCacheFile.CONFIG_HASH_KEY);
698         assertWithMessage("Cash have changed it hash")
699             .that(cacheHash)
700             .isEqualTo(cacheAfterClear.getProperty(PropertyCacheFile.CONFIG_HASH_KEY));
701         final int expectedNumberOfObjectsInCacheAfterSecondRun = 2;
702         assertWithMessage("Cache has changed number of items")
703             .that(cacheAfterSecondRun)
704             .hasSize(expectedNumberOfObjectsInCacheAfterSecondRun);
705     }
706 
707     @Test
708     public void testClearCache() throws Exception {
709         final DefaultConfiguration violationCheck =
710                 createModuleConfig(DummyFileSetViolationCheck.class);
711         final DefaultConfiguration checkerConfig = new DefaultConfiguration("myConfig");
712         checkerConfig.addProperty("charset", "UTF-8");
713         final File cacheFile = createTempFile("junit");
714         checkerConfig.addProperty("cacheFile", cacheFile.getPath());
715         checkerConfig.addChild(violationCheck);
716         final Checker checker = new Checker();
717         checker.setModuleClassLoader(Thread.currentThread().getContextClassLoader());
718         checker.configure(checkerConfig);
719         checker.addListener(getBriefUtLogger());
720 
721         checker.process(Collections.singletonList(new File("dummy.java")));
722         checker.clearCache();
723         // invoke destroy to persist cache
724         final PropertyCacheFile cache = TestUtil.getInternalState(checker,
725                 "cacheFile", PropertyCacheFile.class);
726         cache.persist();
727 
728         final Properties cacheAfterClear = new Properties();
729         try (BufferedReader reader = Files.newBufferedReader(cacheFile.toPath())) {
730             cacheAfterClear.load(reader);
731         }
732 
733         assertWithMessage("Cache has unexpected size")
734             .that(cacheAfterClear)
735             .hasSize(1);
736     }
737 
738     @Test
739     public void setFileExtension() {
740         final Checker checker = new Checker();
741         checker.setFileExtensions(".test1", "test2");
742         final String[] actual = TestUtil.getInternalState(checker,
743                 "fileExtensions", String[].class);
744         assertWithMessage("Extensions are not expected")
745             .that(actual)
746             .isEqualTo(new String[] {".test1", ".test2"});
747     }
748 
749     @Test
750     public void testClearCacheWhenCacheFileIsNotSet() {
751         // The idea of the test is to check that when cache file is not set,
752         // the invocation of clearCache method does not throw an exception.
753         final Checker checker = new Checker();
754         checker.clearCache();
755         final PropertyCacheFile cache = TestUtil.getInternalState(checker,
756                 "cacheFile", PropertyCacheFile.class);
757         assertWithMessage("If cache file is not set the cache should default to null")
758             .that(cache)
759             .isNull();
760     }
761 
762     /**
763      * Test doesn't need to be serialized.
764      *
765      * @noinspection SerializableInnerClassWithNonSerializableOuterClass
766      * @noinspectionreason SerializableInnerClassWithNonSerializableOuterClass - mocked file
767      *      for test does not require serialization
768      */
769     @Test
770     public void testCatchErrorInProcessFilesMethod() {
771         // Assume that I/O error is happened when we try to invoke 'lastModified()' method.
772         final String errorMessage = "Java Virtual Machine is broken"
773             + " or has run out of resources necessary for it to continue operating.";
774         final Error expectedError = new IOError(new InternalError(errorMessage));
775 
776         final File mock = new File("testFile") {
777             @Serial
778             private static final long serialVersionUID = 1L;
779 
780             /**
781              * Test is checking catch clause when exception is thrown.
782              *
783              * @noinspection ProhibitedExceptionThrown
784              * @noinspectionreason ProhibitedExceptionThrown - we require mocked file to
785              *      throw exception as part of test
786              */
787             @Override
788             public long lastModified() {
789                 throw expectedError;
790             }
791         };
792 
793         final Checker checker = new Checker();
794         final List<File> filesToProcess = new ArrayList<>();
795         filesToProcess.add(mock);
796         final Error error =
797                 getExpectedThrowable(Error.class, () -> {
798                     checker.process(filesToProcess);
799                 }, "IOError is expected!");
800         assertWithMessage("Error cause differs from IOError")
801             .that(error.getCause())
802             .isInstanceOf(IOError.class);
803         assertWithMessage("Error cause is not InternalError")
804             .that(error.getCause().getCause())
805             .isInstanceOf(InternalError.class);
806         assertWithMessage("Error message is not expected")
807             .that(error)
808             .hasCauseThat()
809             .hasCauseThat()
810             .hasMessageThat()
811             .isEqualTo(errorMessage);
812         assertWithMessage("Error message differs")
813             .that(error.getMessage())
814             .isEqualTo(getLocalizedMessage(
815                     "Checker.error", mock.getPath()));
816     }
817 
818     /**
819      * Test doesn't need to be serialized.
820      *
821      * @noinspection SerializableInnerClassWithNonSerializableOuterClass
822      * @noinspectionreason SerializableInnerClassWithNonSerializableOuterClass - mocked file
823      *      for test does not require serialization
824      */
825     @Test
826     public void testCatchErrorWithNoFileName() {
827         // Assume that I/O error is happened when we try to invoke 'lastModified()' method.
828         final String errorMessage = "Java Virtual Machine is broken"
829             + " or has run out of resources necessary for it to continue operating.";
830         final Error expectedError = new IOError(new InternalError(errorMessage));
831 
832         final File mock = new File("testFile") {
833             @Serial
834             private static final long serialVersionUID = 1L;
835             /**
836              * Test is checking catch clause when exception is thrown.
837              *
838              * @noinspection ProhibitedExceptionThrown
839              * @noinspectionreason ProhibitedExceptionThrown - we require mocked file to
840              *      throw exception as part of test
841              */
842             @Override
843             public long lastModified() {
844                 throw expectedError;
845             }
846 
847             @Override
848             public String getAbsolutePath() {
849                 return null;
850             }
851         };
852 
853         final Checker checker = new Checker();
854         final List<File> filesToProcess = new ArrayList<>();
855         filesToProcess.add(mock);
856         final Error error =
857                 getExpectedThrowable(Error.class, () -> {
858                     checker.process(filesToProcess);
859                 }, "IOError is expected!");
860         assertWithMessage("Error cause differs from IOError")
861             .that(error)
862             .hasCauseThat()
863             .isInstanceOf(IOError.class);
864         assertWithMessage("Error cause is not InternalError")
865             .that(error)
866             .hasCauseThat()
867             .hasCauseThat()
868             .isInstanceOf(InternalError.class);
869         assertWithMessage("Error message is not expected")
870             .that(error)
871             .hasCauseThat()
872             .hasCauseThat()
873             .hasMessageThat()
874             .isEqualTo(errorMessage);
875     }
876 
877     /**
878      * It is OK to have long test method name here as it describes the test purpose.
879      */
880     @Test
881     public void testCacheAndFilterWhichDoesNotImplementExternalResourceHolderInterface()
882             throws Exception {
883         assertWithMessage("ExternalResourceHolder has changed its parent")
884                 .that(ExternalResourceHolder.class.isAssignableFrom(DummyFilter.class))
885                 .isFalse();
886         final DefaultConfiguration filterConfig = createModuleConfig(DummyFilter.class);
887 
888         final DefaultConfiguration checkerConfig = createRootConfig(filterConfig);
889         final File cacheFile = createTempFile("junit");
890         checkerConfig.addProperty("cacheFile", cacheFile.getPath());
891 
892         final String pathToEmptyFile = createTempFile("file", ".java").getPath();
893 
894         execute(checkerConfig, pathToEmptyFile);
895         final Properties cacheAfterFirstRun = new Properties();
896         try (BufferedReader reader = Files.newBufferedReader(cacheFile.toPath())) {
897             cacheAfterFirstRun.load(reader);
898         }
899 
900         // One more time to use cache.
901         execute(checkerConfig, pathToEmptyFile);
902         final Properties cacheAfterSecondRun = new Properties();
903         try (BufferedReader reader = Files.newBufferedReader(cacheFile.toPath())) {
904             cacheAfterSecondRun.load(reader);
905         }
906 
907         final String cacheFilePath = cacheAfterSecondRun.getProperty(pathToEmptyFile);
908         assertWithMessage("Cache file has changed its path")
909             .that(cacheFilePath)
910             .isEqualTo(cacheAfterFirstRun.getProperty(pathToEmptyFile));
911         final String cacheHash = cacheAfterSecondRun.getProperty(PropertyCacheFile.CONFIG_HASH_KEY);
912         assertWithMessage("Cache has changed its hash")
913             .that(cacheHash)
914             .isEqualTo(cacheAfterFirstRun.getProperty(PropertyCacheFile.CONFIG_HASH_KEY));
915         final int expectedNumberOfObjectsInCache = 2;
916         assertWithMessage("Number of items in cache differs from expected")
917             .that(cacheAfterFirstRun)
918             .hasSize(expectedNumberOfObjectsInCache);
919         assertWithMessage("Number of items in cache differs from expected")
920             .that(cacheAfterSecondRun)
921             .hasSize(expectedNumberOfObjectsInCache);
922     }
923 
924     /**
925      * It is OK to have long test method name here as it describes the test purpose.
926      */
927     // -@cs[ExecutableStatementCount] This test needs to verify many things.
928     @Test
929     public void testCacheAndCheckWhichAddsNewResourceLocationButKeepsSameCheckerInstance()
930             throws Exception {
931         // Use case (https://github.com/checkstyle/checkstyle/pull/3092#issuecomment-218162436):
932         // Imagine that cache exists in a file. New version of Checkstyle appear.
933         // New release contains update to a some check to have additional external resource.
934         // User update his configuration and run validation as usually.
935         // Cache should not be reused.
936 
937         final DynamicalResourceHolderCheck check = new DynamicalResourceHolderCheck();
938         final String firstExternalResourceLocation = getPath("InputCheckerImportControlOne.xml");
939         final String firstExternalResourceKey = PropertyCacheFile.EXTERNAL_RESOURCE_KEY_PREFIX
940                 + firstExternalResourceLocation;
941         check.setFirstExternalResourceLocation(firstExternalResourceLocation);
942 
943         final DefaultConfiguration checkerConfig = createRootConfig(null);
944         final File cacheFile = createTempFile("junit");
945         checkerConfig.addProperty("cacheFile", cacheFile.getPath());
946 
947         final Checker checker = new Checker();
948         checker.setModuleClassLoader(Thread.currentThread().getContextClassLoader());
949         checker.addFileSetCheck(check);
950         checker.addFilter(new DummyFilterSet());
951         checker.configure(checkerConfig);
952         checker.addListener(getBriefUtLogger());
953 
954         final String pathToEmptyFile = createTempFile("file", ".java").getPath();
955 
956         execute(checker, pathToEmptyFile);
957         final Properties cacheAfterFirstRun = new Properties();
958         try (BufferedReader reader = Files.newBufferedReader(cacheFile.toPath())) {
959             cacheAfterFirstRun.load(reader);
960         }
961 
962         final int expectedNumberOfObjectsInCacheAfterFirstRun = 4;
963         assertWithMessage("Number of items in cache differs from expected")
964             .that(cacheAfterFirstRun)
965             .hasSize(expectedNumberOfObjectsInCacheAfterFirstRun);
966 
967         // Change a list of external resources which are used by the check
968         final String secondExternalResourceLocation = "InputCheckerImportControlTwo.xml";
969         final String secondExternalResourceKey = PropertyCacheFile.EXTERNAL_RESOURCE_KEY_PREFIX
970                 + secondExternalResourceLocation;
971         check.setSecondExternalResourceLocation(secondExternalResourceLocation);
972 
973         checker.addFileSetCheck(check);
974         checker.configure(checkerConfig);
975 
976         execute(checker, pathToEmptyFile);
977         final Properties cacheAfterSecondRun = new Properties();
978         try (BufferedReader reader = Files.newBufferedReader(cacheFile.toPath())) {
979             cacheAfterSecondRun.load(reader);
980         }
981 
982         final String cacheFilePath = cacheAfterSecondRun.getProperty(pathToEmptyFile);
983         assertWithMessage("Cache file has changed its path")
984             .that(cacheFilePath)
985             .isEqualTo(cacheAfterFirstRun.getProperty(pathToEmptyFile));
986         final String cacheHash = cacheAfterSecondRun.getProperty(PropertyCacheFile.CONFIG_HASH_KEY);
987         assertWithMessage("Cache has changed its hash")
988             .that(cacheHash)
989             .isEqualTo(cacheAfterFirstRun.getProperty(PropertyCacheFile.CONFIG_HASH_KEY));
990         final String resourceKey = cacheAfterSecondRun.getProperty(firstExternalResourceKey);
991         assertWithMessage("Cache has changed its resource key")
992             .that(resourceKey)
993             .isEqualTo(cacheAfterFirstRun.getProperty(firstExternalResourceKey));
994         assertWithMessage("Cache has null as a resource key")
995             .that(cacheAfterFirstRun.getProperty(firstExternalResourceKey))
996             .isNotNull();
997         final int expectedNumberOfObjectsInCacheAfterSecondRun = 4;
998         assertWithMessage("Number of items in cache differs from expected")
999             .that(cacheAfterSecondRun)
1000             .hasSize(expectedNumberOfObjectsInCacheAfterSecondRun);
1001         assertWithMessage("Cache has not null as a resource key")
1002             .that(cacheAfterFirstRun.getProperty(secondExternalResourceKey))
1003             .isNull();
1004         assertWithMessage("Cache has null as a resource key")
1005             .that(cacheAfterSecondRun.getProperty(secondExternalResourceKey))
1006             .isNotNull();
1007     }
1008 
1009     @Test
1010     public void testClearLazyLoadCacheInDetailAST() throws Exception {
1011 
1012         final String filePath = getPath("InputCheckerClearDetailAstLazyLoadCache.java");
1013 
1014         final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
1015         verifyWithInlineConfigParser(filePath, expected);
1016     }
1017 
1018     @Test
1019     public void testCacheOnViolationSuppression() throws Exception {
1020         final File cacheFile = createTempFile("junit");
1021         final DefaultConfiguration violationCheck =
1022                 createModuleConfig(DummyFileSetViolationCheck.class);
1023 
1024         final DefaultConfiguration filterConfig = createModuleConfig(SuppressionFilter.class);
1025         filterConfig.addProperty("file", getPath("InputCheckerSuppressAll.xml"));
1026 
1027         final DefaultConfiguration checkerConfig = createRootConfig(violationCheck);
1028         checkerConfig.addProperty("cacheFile", cacheFile.getPath());
1029         checkerConfig.addChild(filterConfig);
1030 
1031         final String fileViolationPath = createTempFile("ViolationFile", ".java").getPath();
1032 
1033         execute(checkerConfig, fileViolationPath);
1034 
1035         try (InputStream input = Files.newInputStream(cacheFile.toPath())) {
1036             final Properties details = new Properties();
1037             details.load(input);
1038 
1039             assertWithMessage("suppressed violation file saved in cache")
1040                 .that(details.getProperty(fileViolationPath))
1041                 .isNotNull();
1042         }
1043     }
1044 
1045     @Test
1046     public void testXmlReportFinishedOnProcessingException() throws Exception {
1047         final DefaultConfiguration checkConfig = createModuleConfig(CheckWhichThrowsError.class);
1048         final DefaultConfiguration treewalkerConfig = createModuleConfig(TreeWalker.class);
1049         treewalkerConfig.addChild(checkConfig);
1050         final Checker checker = createChecker(createRootConfig(treewalkerConfig));
1051         final ByteArrayOutputStream output = new ByteArrayOutputStream();
1052         checker.addListener(new XMLLogger(output, OutputStreamOptions.NONE));
1053         final String filePath = getPath("InputChecker.java");
1054 
1055         final CheckstyleException exception = getExpectedThrowable(CheckstyleException.class,
1056                 () -> checker.process(List.of(new File(filePath))), "Exception is expected");
1057 
1058         assertWithMessage("Processing failure must still be propagated")
1059                 .that(exception).hasCauseThat().isInstanceOf(IndexOutOfBoundsException.class);
1060         assertWithMessage("XML report must close the file and audit after a failure")
1061                 .that(output.toString(StandardCharsets.UTF_8))
1062                 .endsWith("</file>" + System.lineSeparator()
1063                         + "</checkstyle>" + System.lineSeparator());
1064         checker.destroy();
1065     }
1066 
1067     @Test
1068     public void testProcessingExceptionPreservedWhenCompletionFails() throws Exception {
1069         final DefaultConfiguration checkConfig = createModuleConfig(CheckWhichThrowsError.class);
1070         final DefaultConfiguration treewalkerConfig = createModuleConfig(TreeWalker.class);
1071         treewalkerConfig.addChild(checkConfig);
1072         final Checker checker = createChecker(createRootConfig(treewalkerConfig));
1073         final IllegalStateException fileFailure = new IllegalStateException("file completion");
1074         final IllegalStateException auditFailure = new IllegalStateException("audit completion");
1075         checker.addListener(new DefaultLogger(new ByteArrayOutputStream(),
1076                 OutputStreamOptions.NONE) {
1077             @Override
1078             public void fileFinished(AuditEvent event) {
1079                 throw fileFailure;
1080             }
1081 
1082             @Override
1083             public void auditFinished(AuditEvent event) {
1084                 throw auditFailure;
1085             }
1086         });
1087 
1088         final CheckstyleException exception = getExpectedThrowable(CheckstyleException.class,
1089                 () -> checker.process(List.of(new File(getPath("InputChecker.java")))),
1090                 "Processing exception is expected");
1091 
1092         assertWithMessage("Processing failure must remain the primary cause")
1093                 .that(exception).hasCauseThat().isInstanceOf(IndexOutOfBoundsException.class);
1094         assertWithMessage("File completion failure must be suppressed")
1095                 .that(exception.getCause().getSuppressed()).asList().containsExactly(fileFailure);
1096         assertWithMessage("Audit completion failure must be suppressed")
1097                 .that(exception.getSuppressed()).asList().containsExactly(auditFailure);
1098         checker.destroy();
1099     }
1100 
1101     @Test
1102     public void testHaltOnException() throws Exception {
1103         final DefaultConfiguration checkConfig =
1104             createModuleConfig(CheckWhichThrowsError.class);
1105         final String filePath = getPath("InputChecker.java");
1106         final CheckstyleException exc =
1107                 getExpectedThrowable(CheckstyleException.class, () -> {
1108                     execute(checkConfig, filePath);
1109                 }, "Exception is expected");
1110         assertWithMessage("Error message is not expected")
1111             .that(exc.getMessage())
1112             .isEqualTo("Exception was thrown while processing " + filePath);
1113     }
1114 
1115     @Test
1116     public void testExceptionWithCache() throws Exception {
1117         final File cacheFile = createTempFile("junit");
1118 
1119         final DefaultConfiguration checkConfig =
1120                 createModuleConfig(CheckWhichThrowsError.class);
1121 
1122         final DefaultConfiguration treewalkerConfig =
1123                 createModuleConfig(TreeWalker.class);
1124         treewalkerConfig.addChild(checkConfig);
1125 
1126         final DefaultConfiguration checkerConfig = createRootConfig(treewalkerConfig);
1127         checkerConfig.addProperty("charset", StandardCharsets.UTF_8.name());
1128         checkerConfig.addProperty("cacheFile", cacheFile.getPath());
1129         checkerConfig.addChild(treewalkerConfig);
1130 
1131         final Checker checker = createChecker(checkerConfig);
1132 
1133         final String filePath = getPath("InputChecker.java");
1134         final CheckstyleException exc =
1135                 getExpectedThrowable(CheckstyleException.class, () -> {
1136                     checker.process(Collections.singletonList(new File(filePath)));
1137                 }, "Exception is expected");
1138         assertWithMessage("Error message is not expected")
1139             .that(exc.getMessage())
1140             .isEqualTo("Exception was thrown while processing " + filePath);
1141 
1142         // destroy is called by Main
1143         checker.destroy();
1144 
1145         final Properties cache = new Properties();
1146         try (BufferedReader reader = Files.newBufferedReader(cacheFile.toPath())) {
1147             cache.load(reader);
1148         }
1149 
1150         assertWithMessage("Cache has unexpected size")
1151             .that(cache)
1152             .hasSize(1);
1153         assertWithMessage("testFile is not in cache")
1154             .that(cache.getProperty(filePath))
1155             .isNull();
1156     }
1157 
1158     /**
1159      * Test doesn't need to be serialized.
1160      *
1161      * @noinspection SerializableInnerClassWithNonSerializableOuterClass
1162      * @noinspectionreason SerializableInnerClassWithNonSerializableOuterClass - mocked file
1163      *      for test does not require serialization
1164      */
1165     @Test
1166     public void testCatchErrorWithCache() throws Exception {
1167         final File cacheFile = createTempFile("junit");
1168 
1169         final DefaultConfiguration checkerConfig = new DefaultConfiguration("configuration");
1170         checkerConfig.addProperty("charset", StandardCharsets.UTF_8.name());
1171         checkerConfig.addProperty("cacheFile", cacheFile.getPath());
1172 
1173         final String errorMessage = "Java Virtual Machine is broken"
1174             + " or has run out of resources necessary for it to continue operating.";
1175         final Error expectedError = new IOError(new InternalError(errorMessage));
1176 
1177         final File mock = new File("testFile") {
1178             @Serial
1179             private static final long serialVersionUID = 1L;
1180             @Override
1181             public String getAbsolutePath() {
1182                 return "testFile";
1183             }
1184 
1185             /**
1186              * Test is checking catch clause when exception is thrown.
1187              *
1188              * @noinspection ProhibitedExceptionThrown
1189              * @noinspectionreason ProhibitedExceptionThrown - we require mocked file to
1190              *      throw exception as part of test
1191              */
1192             @Override
1193             public File getAbsoluteFile() {
1194                 throw expectedError;
1195             }
1196         };
1197 
1198         final Checker checker = new Checker();
1199         checker.setModuleClassLoader(Thread.currentThread().getContextClassLoader());
1200         checker.configure(checkerConfig);
1201         final ByteArrayOutputStream output = new ByteArrayOutputStream();
1202         checker.addListener(new XMLLogger(output, OutputStreamOptions.NONE));
1203         final List<File> filesToProcess = new ArrayList<>();
1204         filesToProcess.add(mock);
1205         final Error error =
1206                 getExpectedThrowable(Error.class, () -> {
1207                     checker.process(filesToProcess);
1208                 }, "IOError is expected!");
1209         assertWithMessage("Error cause differs from IOError")
1210             .that(error.getCause())
1211             .isInstanceOf(IOError.class);
1212         assertWithMessage("Error message is not expected")
1213             .that(error)
1214             .hasCauseThat()
1215             .hasCauseThat()
1216             .hasMessageThat()
1217             .isEqualTo(errorMessage);
1218 
1219         // destroy is called by Main
1220         checker.destroy();
1221 
1222         assertWithMessage("XML report must close the file and audit after an error")
1223                 .that(output.toString(StandardCharsets.UTF_8))
1224                 .endsWith("</file>" + System.lineSeparator()
1225                         + "</checkstyle>" + System.lineSeparator());
1226 
1227         final Properties cache = new Properties();
1228         try (BufferedReader reader = Files.newBufferedReader(cacheFile.toPath())) {
1229             cache.load(reader);
1230         }
1231 
1232         assertWithMessage("Cache has unexpected size")
1233             .that(cache)
1234             .hasSize(1);
1235         assertWithMessage("testFile is not in cache")
1236             .that(cache.getProperty("testFile"))
1237             .isNull();
1238     }
1239 
1240     /**
1241      * Test doesn't need to be serialized.
1242      *
1243      * @noinspection SerializableInnerClassWithNonSerializableOuterClass
1244      * @noinspectionreason SerializableInnerClassWithNonSerializableOuterClass - mocked file
1245      *      for test does not require serialization
1246      */
1247     @Test
1248     public void testCatchErrorWithCacheWithNoFileName() throws Exception {
1249         final File cacheFile = createTempFile("junit");
1250 
1251         final DefaultConfiguration checkerConfig = new DefaultConfiguration("configuration");
1252         checkerConfig.addProperty("charset", StandardCharsets.UTF_8.name());
1253         checkerConfig.addProperty("cacheFile", cacheFile.getPath());
1254 
1255         final String errorMessage = "Java Virtual Machine is broken"
1256             + " or has run out of resources necessary for it to continue operating.";
1257         final Error expectedError = new IOError(new InternalError(errorMessage));
1258 
1259         final File mock = new File("testFile") {
1260             @Serial
1261             private static final long serialVersionUID = 1L;
1262 
1263             /**
1264              * Test is checking catch clause when exception is thrown.
1265              *
1266              * @noinspection ProhibitedExceptionThrown
1267              * @noinspectionreason ProhibitedExceptionThrown - we require mocked file to
1268              *      throw exception as part of test
1269              */
1270             @Override
1271             public String getAbsolutePath() {
1272                 throw expectedError;
1273             }
1274         };
1275 
1276         final Checker checker = new Checker();
1277         checker.setModuleClassLoader(Thread.currentThread().getContextClassLoader());
1278         checker.configure(checkerConfig);
1279         final List<File> filesToProcess = new ArrayList<>();
1280         filesToProcess.add(mock);
1281         final Error error =
1282                 getExpectedThrowable(Error.class, () -> {
1283                     checker.process(filesToProcess);
1284                 }, "IOError is expected!");
1285         assertWithMessage("Error cause differs from IOError")
1286             .that(error)
1287             .hasCauseThat()
1288             .isInstanceOf(IOError.class);
1289         assertWithMessage("Error message is not expected")
1290             .that(error)
1291             .hasCauseThat()
1292             .hasCauseThat()
1293             .hasMessageThat()
1294             .isEqualTo(errorMessage);
1295 
1296         // destroy is called by Main
1297         checker.destroy();
1298 
1299         final Properties cache = new Properties();
1300         try (BufferedReader reader = Files.newBufferedReader(cacheFile.toPath())) {
1301             cache.load(reader);
1302         }
1303 
1304         assertWithMessage("Cache has unexpected size")
1305             .that(cache)
1306             .hasSize(1);
1307     }
1308 
1309     /**
1310      * Test doesn't need to be serialized.
1311      *
1312      * @noinspection SerializableInnerClassWithNonSerializableOuterClass
1313      * @noinspectionreason SerializableInnerClassWithNonSerializableOuterClass - mocked file
1314      *      for test does not require serialization
1315      */
1316     @Test
1317     public void testExceptionWithNoFileName() {
1318         final String errorMessage = "Security Exception";
1319         final RuntimeException expectedError = new SecurityException(errorMessage);
1320 
1321         final File mock = new File("testFile") {
1322             @Serial
1323             private static final long serialVersionUID = 1L;
1324 
1325             /**
1326              * Test is checking catch clause when exception is thrown.
1327              *
1328              * @noinspection ProhibitedExceptionThrown
1329              * @noinspectionreason ProhibitedExceptionThrown - we require mocked file to
1330              *      throw exception as part of test
1331              */
1332             @Override
1333             public String getAbsolutePath() {
1334                 throw expectedError;
1335             }
1336         };
1337 
1338         final Checker checker = new Checker();
1339         final List<File> filesToProcess = new ArrayList<>();
1340         filesToProcess.add(mock);
1341         final CheckstyleException exc =
1342                 getExpectedThrowable(CheckstyleException.class, () -> {
1343                     checker.process(filesToProcess);
1344                 }, "SecurityException is expected!");
1345         assertWithMessage("Error cause differs from SecurityException")
1346             .that(exc)
1347             .hasCauseThat()
1348             .isInstanceOf(SecurityException.class);
1349         assertWithMessage("Error message is not expected")
1350             .that(exc)
1351             .hasCauseThat()
1352             .hasMessageThat()
1353             .isEqualTo(errorMessage);
1354     }
1355 
1356     /**
1357      * Test doesn't need to be serialized.
1358      *
1359      * @noinspection SerializableInnerClassWithNonSerializableOuterClass
1360      * @noinspectionreason SerializableInnerClassWithNonSerializableOuterClass - mocked file
1361      *      for test does not require serialization
1362      */
1363     @Test
1364     public void testExceptionWithCacheAndNoFileName() throws Exception {
1365         final File cacheFile = createTempFile("junit");
1366 
1367         final DefaultConfiguration checkerConfig = new DefaultConfiguration("configuration");
1368         checkerConfig.addProperty("charset", StandardCharsets.UTF_8.name());
1369         checkerConfig.addProperty("cacheFile", cacheFile.getPath());
1370 
1371         final String errorMessage = "Security Exception";
1372         final RuntimeException expectedError = new SecurityException(errorMessage);
1373 
1374         final File mock = new File("testFile") {
1375             @Serial
1376             private static final long serialVersionUID = 1L;
1377 
1378             /**
1379              * Test is checking catch clause when exception is thrown.
1380              *
1381              * @noinspection ProhibitedExceptionThrown
1382              * @noinspectionreason ProhibitedExceptionThrown - we require mocked file to
1383              *      throw exception as part of test
1384              */
1385             @Override
1386             public String getAbsolutePath() {
1387                 throw expectedError;
1388             }
1389         };
1390 
1391         final Checker checker = new Checker();
1392         checker.setModuleClassLoader(Thread.currentThread().getContextClassLoader());
1393         checker.configure(checkerConfig);
1394         final List<File> filesToProcess = new ArrayList<>();
1395         filesToProcess.add(mock);
1396         final CheckstyleException exc =
1397                 getExpectedThrowable(CheckstyleException.class, () -> {
1398                     checker.process(filesToProcess);
1399                 }, "SecurityException is expected!");
1400         assertWithMessage("Error cause differs from SecurityException")
1401             .that(exc)
1402             .hasCauseThat()
1403             .isInstanceOf(SecurityException.class);
1404         assertWithMessage("Error message is not expected")
1405             .that(exc)
1406             .hasCauseThat()
1407             .hasMessageThat()
1408             .isEqualTo(errorMessage);
1409 
1410         // destroy is called by Main
1411         checker.destroy();
1412 
1413         final Properties cache = new Properties();
1414         try (BufferedReader reader = Files.newBufferedReader(cacheFile.toPath())) {
1415             cache.load(reader);
1416         }
1417 
1418         assertWithMessage("Cache has unexpected size")
1419             .that(cache)
1420             .hasSize(1);
1421     }
1422 
1423     @Test
1424     public void testHaltOnExceptionOff() throws Exception {
1425         final String filePath = getPath("InputChecker.java");
1426         final String[] expected = {
1427             "1: " + getCheckMessage(EXCEPTION_MSG, "java.lang.IndexOutOfBoundsException: test"),
1428         };
1429 
1430         verifyWithInlineXmlConfig(filePath, expected);
1431     }
1432 
1433     @Test
1434     public void testTabViolationDefault() throws Exception {
1435         final String[] expected = {
1436             "18:17: violation",
1437             "22:49: violation",
1438         };
1439         verifyWithInlineConfigParser(getPath("InputCheckerTabCharacter.java"),
1440             expected);
1441     }
1442 
1443     @Test
1444     public void testTabViolationCustomWidth() throws Exception {
1445         final String[] expected = {
1446             "19:17: violation",
1447             "23:37: violation",
1448         };
1449 
1450         verifyWithInlineXmlConfig(getPath("InputCheckerTabCharacterCustomWidth.java"), expected);
1451     }
1452 
1453     @Test
1454     public void testCheckerProcessCallAllNeededMethodsOfFileSets() throws Exception {
1455         final DummyFileSet fileSet = new DummyFileSet();
1456         final Checker checker = new Checker();
1457         checker.addFileSetCheck(fileSet);
1458         checker.process(Collections.singletonList(new File("dummy.java")));
1459         final List<String> expected =
1460             Arrays.asList("beginProcessing", "finishProcessing", "destroy");
1461         assertWithMessage("Method calls were not expected")
1462             .that(fileSet.getMethodCalls())
1463             .isEqualTo(expected);
1464     }
1465 
1466     @Test
1467     public void testSetFileSetCheckSetsMessageDispatcher() {
1468         final DummyFileSet fileSet = new DummyFileSet();
1469         final Checker checker = new Checker();
1470         checker.addFileSetCheck(fileSet);
1471         assertWithMessage("Message dispatcher was not expected")
1472             .that(fileSet.getInternalMessageDispatcher())
1473             .isEqualTo(checker);
1474     }
1475 
1476     @Test
1477     public void testAddAuditListenerAsChild() throws Exception {
1478         final Checker checker = new Checker();
1479         final DebugAuditAdapter auditAdapter = new DebugAuditAdapter();
1480         final PackageObjectFactory factory = new PackageObjectFactory(
1481                 new HashSet<>(), Thread.currentThread().getContextClassLoader()) {
1482             @Override
1483             public Object createModule(String name) throws CheckstyleException {
1484                 Object adapter = auditAdapter;
1485                 if (!name.equals(DebugAuditAdapter.class.getName())) {
1486                     adapter = super.createModule(name);
1487                 }
1488                 return adapter;
1489             }
1490         };
1491         checker.setModuleFactory(factory);
1492         checker.setupChild(createModuleConfig(DebugAuditAdapter.class));
1493         // Let's try fire some events
1494         checker.process(Collections.singletonList(new File("dummy.java")));
1495         assertWithMessage("Checker.fireAuditStarted() doesn't call listener")
1496                 .that(auditAdapter.wasCalled())
1497                 .isTrue();
1498     }
1499 
1500     @Test
1501     public void testAddBeforeExecutionFileFilterAsChild() throws Exception {
1502         final Checker checker = new Checker();
1503         final TestBeforeExecutionFileFilter fileFilter = new TestBeforeExecutionFileFilter();
1504         final PackageObjectFactory factory = new PackageObjectFactory(
1505                 new HashSet<>(), Thread.currentThread().getContextClassLoader()) {
1506             @Override
1507             public Object createModule(String name) throws CheckstyleException {
1508                 Object filter = fileFilter;
1509                 if (!name.equals(TestBeforeExecutionFileFilter.class.getName())) {
1510                     filter = super.createModule(name);
1511                 }
1512                 return filter;
1513             }
1514         };
1515         checker.setModuleFactory(factory);
1516         checker.setupChild(createModuleConfig(TestBeforeExecutionFileFilter.class));
1517         checker.process(Collections.singletonList(new File("dummy.java")));
1518         assertWithMessage("Checker.acceptFileStarted() doesn't call listener")
1519                 .that(fileFilter.wasCalled())
1520                 .isTrue();
1521     }
1522 
1523     @Test
1524     public void testFileSetCheckInitWhenAddedAsChild() throws Exception {
1525         final Checker checker = new Checker();
1526         final DummyFileSet fileSet = new DummyFileSet();
1527         final PackageObjectFactory factory = new PackageObjectFactory(
1528                 new HashSet<>(), Thread.currentThread().getContextClassLoader()) {
1529             @Override
1530             public Object createModule(String name) throws CheckstyleException {
1531                 Object check = fileSet;
1532                 if (!name.equals(DummyFileSet.class.getName())) {
1533                     check = super.createModule(name);
1534                 }
1535                 return check;
1536             }
1537         };
1538         checker.setModuleFactory(factory);
1539         checker.finishLocalSetup();
1540         checker.setupChild(createModuleConfig(DummyFileSet.class));
1541         assertWithMessage("FileSetCheck.init() wasn't called")
1542                 .that(fileSet.isInitCalled())
1543                 .isTrue();
1544     }
1545 
1546     // -@cs[CheckstyleTestMakeup] must use raw class to directly initialize DefaultLogger
1547     @Test
1548     public void testDefaultLoggerClosesItStreams() throws Exception {
1549         final Checker checker = new Checker();
1550         try (CloseAndFlushTestByteArrayOutputStream testInfoOutputStream =
1551                 new CloseAndFlushTestByteArrayOutputStream();
1552             CloseAndFlushTestByteArrayOutputStream testErrorOutputStream =
1553                 new CloseAndFlushTestByteArrayOutputStream()) {
1554             checker.setModuleClassLoader(Thread.currentThread().getContextClassLoader());
1555             checker.addListener(new DefaultLogger(testInfoOutputStream,
1556                 OutputStreamOptions.CLOSE, testErrorOutputStream, OutputStreamOptions.CLOSE));
1557 
1558             final File tmpFile = createTempFile("file", ".java");
1559 
1560             execute(checker, tmpFile.getPath());
1561 
1562             assertWithMessage("Output stream close count")
1563                     .that(testInfoOutputStream.getCloseCount())
1564                     .isEqualTo(1);
1565             assertWithMessage("Output stream flush count")
1566                     .that(testInfoOutputStream.getFlushCount())
1567                     .isEqualTo(TestUtil.adjustFlushCountForOutputStreamClose(3));
1568             assertWithMessage("Error stream close count")
1569                     .that(testErrorOutputStream.getCloseCount())
1570                     .isEqualTo(1);
1571             assertWithMessage("Error stream flush count")
1572                     .that(testErrorOutputStream.getFlushCount())
1573                     .isEqualTo(TestUtil.adjustFlushCountForOutputStreamClose(1));
1574         }
1575     }
1576 
1577     // -@cs[CheckstyleTestMakeup] must use raw class to directly initialize DefaultLogger
1578     @Test
1579     public void testXmlLoggerClosesItStreams() throws Exception {
1580         final Checker checker = new Checker();
1581         try (CloseAndFlushTestByteArrayOutputStream testInfoOutputStream =
1582                 new CloseAndFlushTestByteArrayOutputStream()) {
1583             checker.setModuleClassLoader(Thread.currentThread().getContextClassLoader());
1584             checker.addListener(new XMLLogger(testInfoOutputStream, OutputStreamOptions.CLOSE));
1585 
1586             final File tmpFile = createTempFile("file", ".java");
1587 
1588             execute(checker, tmpFile.getPath(), tmpFile.getPath());
1589 
1590             assertWithMessage("Output stream close count")
1591                     .that(testInfoOutputStream.getCloseCount())
1592                     .isEqualTo(1);
1593             assertWithMessage("Output stream flush count")
1594                     .that(testInfoOutputStream.getFlushCount())
1595                     .isEqualTo(TestUtil.adjustFlushCountForOutputStreamClose(0));
1596         }
1597     }
1598 
1599     @Test
1600     public void testDuplicatedModule() throws Exception {
1601         // we need to test a module with two instances, one with id and the other not
1602         final DefaultConfiguration moduleConfig1 =
1603                 createModuleConfig(NewlineAtEndOfFileCheck.class);
1604         final DefaultConfiguration moduleConfig2 =
1605                 createModuleConfig(NewlineAtEndOfFileCheck.class);
1606         moduleConfig2.addProperty("id", "ModuleId");
1607         final DefaultConfiguration root = new DefaultConfiguration("root");
1608         root.addChild(moduleConfig1);
1609         root.addChild(moduleConfig2);
1610         final Checker checker = new Checker();
1611         checker.setModuleClassLoader(Thread.currentThread().getContextClassLoader());
1612         checker.configure(root);
1613         // BriefUtLogger does not print the module name or id postfix,
1614         // so we need to set logger manually
1615         final ByteArrayOutputStream out = TestUtil.getInternalState(this,
1616                 "stream", ByteArrayOutputStream.class);
1617         final DefaultLogger logger = new DefaultLogger(out, OutputStreamOptions.CLOSE, out,
1618                 OutputStreamOptions.NONE, new AuditEventDefaultFormatter());
1619         checker.addListener(logger);
1620 
1621         final File tempFile = createTempFile("file", ".java");
1622         try (BufferedWriter bufferedWriter = Files.newBufferedWriter(tempFile.toPath())) {
1623             bufferedWriter.write(';');
1624         }
1625         final String path = tempFile.getPath();
1626         final String violationMessage =
1627                 getCheckMessage(NewlineAtEndOfFileCheck.class, MSG_KEY_NO_NEWLINE_EOF);
1628         final String[] expected = {
1629             "1: " + violationMessage + " [NewlineAtEndOfFile]",
1630             "1: " + violationMessage + " [ModuleId]",
1631         };
1632 
1633         // super.verify does not work here, for we change the logger
1634         out.flush();
1635         final int errs = checker.process(Collections.singletonList(new File(path)));
1636         try (ByteArrayInputStream inputStream =
1637                 new ByteArrayInputStream(out.toByteArray());
1638             LineNumberReader lnr = new LineNumberReader(
1639                 new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
1640             // we need to ignore the unrelated lines
1641             final List<String> actual = lnr.lines()
1642                     .filter(line -> !getCheckMessage(AUDIT_STARTED_MESSAGE).equals(line))
1643                     .filter(line -> !getCheckMessage(AUDIT_FINISHED_MESSAGE).equals(line))
1644                     .limit(expected.length)
1645                     .sorted()
1646                     .toList();
1647             Arrays.sort(expected);
1648 
1649             for (int index = 0; index < expected.length; index++) {
1650                 final String expectedResult = "[ERROR] " + path + ":" + expected[index];
1651                 assertWithMessage("error message %s", index)
1652                         .that(actual.get(index))
1653                         .isEqualTo(expectedResult);
1654             }
1655 
1656             assertWithMessage("unexpected output: %s", lnr.readLine())
1657                     .that(errs)
1658                     .isEqualTo(expected.length);
1659         }
1660 
1661         checker.destroy();
1662     }
1663 
1664     @Test
1665     public void testCachedFile() throws Exception {
1666         final Checker checker = createChecker(createModuleConfig(TranslationCheck.class));
1667         final OutputStream infoStream = new ByteArrayOutputStream();
1668         final OutputStream errorStream = new ByteArrayOutputStream();
1669         final DefaultLoggerWithCounter loggerWithCounter =
1670             new DefaultLoggerWithCounter(infoStream, OutputStreamOptions.CLOSE,
1671                                          errorStream, OutputStreamOptions.CLOSE);
1672         checker.addListener(loggerWithCounter);
1673         final File cacheFile = createTempFile("cacheFile", ".txt");
1674         checker.setCacheFile(cacheFile.getAbsolutePath());
1675 
1676         final File testFile = createTempFile("testFile", ".java");
1677         final List<File> files = List.of(testFile, testFile);
1678         checker.process(files);
1679 
1680         assertWithMessage("Cached file should not be processed twice")
1681             .that(loggerWithCounter.fileStartedCount)
1682             .isEqualTo(1);
1683         assertWithMessage("Cached file should not receive a second completion event")
1684             .that(loggerWithCounter.fileFinishedCount)
1685             .isEqualTo(1);
1686 
1687         checker.destroy();
1688     }
1689 
1690     @Test
1691     public void testUnmappableCharacters() throws Exception {
1692         final String[] expected = {
1693             "14: " + getCheckMessage(LineLengthCheck.class, MSG_KEY, 80, 225),
1694         };
1695 
1696         verifyWithInlineXmlConfig(getPath("InputCheckerTestCharset.java"),
1697                 expected);
1698     }
1699 
1700     /**
1701      * This tests uses 'verify' method, because it needs some config
1702      * to be executed on non-existing Input file,
1703      * but BDD style methods need config in existing file.
1704      *
1705      * @throws Exception exception
1706      */
1707     @SuppressForbidden
1708     @Test
1709     public void testViolationMessageOnIoException() throws Exception {
1710         final DefaultConfiguration checkConfig =
1711                 createModuleConfig(CheckWhichThrowsError.class);
1712 
1713         final DefaultConfiguration treeWalkerConfig = createModuleConfig(TreeWalker.class);
1714         treeWalkerConfig.addChild(checkConfig);
1715 
1716         final DefaultConfiguration checkerConfig = createRootConfig(treeWalkerConfig);
1717         checkerConfig.addChild(treeWalkerConfig);
1718 
1719         checkerConfig.addProperty("haltOnException", "false");
1720         final File file = new File("InputNonChecker.java");
1721         final String filePath = file.getAbsolutePath();
1722         final String[] expected = {
1723             "1: " + getCheckMessage(EXCEPTION_MSG, filePath
1724                         + " (No such file or directory)"),
1725         };
1726 
1727         verify(checkerConfig, filePath, expected);
1728     }
1729 
1730     /**
1731      * Reason of non-Input based testing:
1732      * There are bunch of asserts that expects full path to file,
1733      * usage of "basedir" make it stripped and we need put everywhere code like
1734      * {@code CommonUtil.relativizePath(checker.getConfiguration().getProperty("basedir"), file)}
1735      * but Checker object is not always available in code.
1736      * Propagating it in all code methods will complicate code.
1737      */
1738     @Test
1739     public void testRelativizedFileExclusion() throws Exception {
1740         final DefaultConfiguration newLineAtEndOfFileConfig =
1741                 createModuleConfig(NewlineAtEndOfFileCheck.class);
1742 
1743         final DefaultConfiguration beforeExecutionExclusionFileFilterConfig =
1744                 createModuleConfig(BeforeExecutionExclusionFileFilter.class);
1745 
1746         beforeExecutionExclusionFileFilterConfig.addProperty("fileNamePattern",
1747                         "^(?!InputCheckerTestExcludeRelativizedFile.*\\.java).*");
1748 
1749         final DefaultConfiguration checkerConfig = createRootConfig(null);
1750         checkerConfig.addChild(newLineAtEndOfFileConfig);
1751         checkerConfig.addChild(beforeExecutionExclusionFileFilterConfig);
1752 
1753         // -@cs[CheckstyleTestMakeup] Needs to be fixed.
1754         checkerConfig.addProperty("basedir",
1755                 temporaryFolder.getPath());
1756 
1757         final String violationMessage =
1758                 getCheckMessage(NewlineAtEndOfFileCheck.class, MSG_KEY_NO_NEWLINE_EOF);
1759 
1760         final String[] expected = {
1761             "1: " + violationMessage,
1762         };
1763 
1764         final File tempFile = createTempFile("InputCheckerTestExcludeRelativizedFile", ".java");
1765         try (BufferedWriter bufferedWriter = Files.newBufferedWriter(tempFile.toPath())) {
1766             bufferedWriter.write(';');
1767         }
1768 
1769         final File[] processedFiles = {tempFile};
1770 
1771         verify(createChecker(checkerConfig), processedFiles,
1772                 tempFile.getName(), expected);
1773     }
1774 
1775     @Test
1776     public void testBasedirPathMismatchExceptionMessage() throws IOException {
1777         final CheckstyleAntTask antTask = new CheckstyleAntTask();
1778         antTask.setProject(new Project());
1779         antTask.setConfig(getPath("InputCheckerTestConfigBasedirMismatch.xml"));
1780 
1781         final File testFile = Files.createFile(
1782             temporaryFolder.toPath().resolve("TestBasedirMismatch.java"))
1783             .toFile();
1784 
1785         antTask.setFile(testFile);
1786 
1787         final BuildException ex = getExpectedThrowable(
1788             BuildException.class,
1789             antTask::execute,
1790             "BuildException is expected");
1791 
1792         final IllegalStateException relativizeException = findRelativizePathException(ex);
1793         final String message = relativizeException.getMessage();
1794 
1795         assertWithMessage("Exception message should mention failed relativization")
1796             .that(message)
1797             .contains("Failed to relativize path");
1798 
1799         assertWithMessage("Exception message should mention base directory")
1800             .that(message)
1801             .contains("base directory");
1802 
1803         assertWithMessage("Exception message should contain the file path")
1804             .that(message)
1805             .contains(testFile.getAbsolutePath());
1806     }
1807 
1808     private static IllegalStateException findRelativizePathException(Throwable throwable) {
1809         Throwable current = throwable;
1810         while (current != null) {
1811             if (current instanceof IllegalStateException exception
1812                     && current.getCause() instanceof IllegalArgumentException) {
1813                 return exception;
1814             }
1815             current = current.getCause();
1816         }
1817         throw new AssertionError(
1818             "IllegalStateException from CommonUtil.relativizePath not found in cause chain");
1819     }
1820 
1821     public static class DefaultLoggerWithCounter extends DefaultLogger {
1822 
1823         private int fileStartedCount;
1824         private int fileFinishedCount;
1825 
1826         public DefaultLoggerWithCounter(OutputStream infoStream,
1827                                         OutputStreamOptions infoStreamOptions,
1828                                         OutputStream errorStream,
1829                                         OutputStreamOptions errorStreamOptions) {
1830             super(infoStream, infoStreamOptions, errorStream, errorStreamOptions);
1831         }
1832 
1833         @Override
1834         public void fileStarted(AuditEvent event) {
1835             fileStartedCount++;
1836         }
1837 
1838         @Override
1839         public void fileFinished(AuditEvent event) {
1840             fileFinishedCount++;
1841         }
1842     }
1843 
1844     public static class DummyFilter implements Filter {
1845 
1846         @Override
1847         public boolean accept(AuditEvent event) {
1848             return false;
1849         }
1850 
1851     }
1852 
1853     public static class DummyFileSetViolationCheck extends AbstractFileSetCheck
1854         implements ExternalResourceHolder {
1855 
1856         @Override
1857         protected void processFiltered(File file, FileText fileText) {
1858             log(1, "test");
1859         }
1860 
1861         @Override
1862         public Set<String> getExternalResourceLocations() {
1863             final Set<String> externalResourceLocation = new HashSet<>(1);
1864             externalResourceLocation.add("non_existent_external_resource.xml");
1865             return externalResourceLocation;
1866         }
1867 
1868     }
1869 
1870     public static class DummyFilterSet extends FilterSet implements ExternalResourceHolder {
1871 
1872         @Override
1873         public Set<String> getExternalResourceLocations() {
1874             final Set<String> strings = new HashSet<>();
1875             strings.add("test");
1876             return strings;
1877         }
1878 
1879     }
1880 
1881     public static final class DynamicalResourceHolderCheck extends AbstractFileSetCheck
1882         implements ExternalResourceHolder {
1883 
1884         private String firstExternalResourceLocation;
1885         private String secondExternalResourceLocation;
1886 
1887         public void setFirstExternalResourceLocation(String firstExternalResourceLocation) {
1888             this.firstExternalResourceLocation = firstExternalResourceLocation;
1889         }
1890 
1891         public void setSecondExternalResourceLocation(String secondExternalResourceLocation) {
1892             this.secondExternalResourceLocation = secondExternalResourceLocation;
1893         }
1894 
1895         @Override
1896         protected void processFiltered(File file, FileText fileText) {
1897             // there is no need in implementation of the method
1898         }
1899 
1900         @Override
1901         public Set<String> getExternalResourceLocations() {
1902             final Set<String> locations = new HashSet<>();
1903             locations.add(firstExternalResourceLocation);
1904             // Attempt to change the behaviour of the check dynamically
1905             if (secondExternalResourceLocation != null) {
1906                 locations.add(secondExternalResourceLocation);
1907             }
1908             return locations;
1909         }
1910 
1911     }
1912 
1913     public static class CheckWhichDoesNotRequireCommentNodes extends AbstractCheck {
1914 
1915         /** Number of children of method definition token. */
1916         private static final int METHOD_DEF_CHILD_COUNT = 7;
1917 
1918         @Override
1919         public int[] getDefaultTokens() {
1920             return new int[] {TokenTypes.METHOD_DEF};
1921         }
1922 
1923         @Override
1924         public int[] getAcceptableTokens() {
1925             return new int[] {TokenTypes.METHOD_DEF};
1926         }
1927 
1928         @Override
1929         public int[] getRequiredTokens() {
1930             return new int[] {TokenTypes.METHOD_DEF};
1931         }
1932 
1933         @Override
1934         public void visitToken(DetailAST ast) {
1935             if (ast.findFirstToken(TokenTypes.MODIFIERS).findFirstToken(
1936                     TokenTypes.BLOCK_COMMENT_BEGIN) != null) {
1937                 log(ast, "AST has incorrect structure structure."
1938                     + " The check does not require comment nodes but there were comment nodes"
1939                     + " in the AST.");
1940             }
1941             final int childCount = ast.getChildCount();
1942             if (childCount != METHOD_DEF_CHILD_COUNT) {
1943                 final String msg = String.format(Locale.ENGLISH,
1944                     "AST node in no comment tree has wrong number of children. "
1945                             + "Expected is %d but was %d",
1946                     METHOD_DEF_CHILD_COUNT, childCount);
1947                 log(ast, msg);
1948             }
1949             // count children where comment lives
1950             int actualChildCount = 0;
1951             for (DetailAST child = ast.getFirstChild().getFirstChild(); child != null; child =
1952                     child.getNextSibling()) {
1953                 actualChildCount++;
1954             }
1955             final int cacheChildCount = ast.getFirstChild().getChildCount();
1956             if (cacheChildCount != actualChildCount) {
1957                 final String msg = String.format(Locale.ENGLISH,
1958                         "AST node with no comment has wrong number of children. "
1959                                 + "Expected is %d but was %d",
1960                         cacheChildCount, actualChildCount);
1961                 log(ast, msg);
1962             }
1963         }
1964 
1965     }
1966 
1967     public static class CheckWhichRequiresCommentNodes extends AbstractCheck {
1968 
1969         /** Number of children of method definition token. */
1970         private static final int METHOD_DEF_CHILD_COUNT = 7;
1971 
1972         @Override
1973         public boolean isCommentNodesRequired() {
1974             return true;
1975         }
1976 
1977         @Override
1978         public int[] getDefaultTokens() {
1979             return new int[] {TokenTypes.METHOD_DEF};
1980         }
1981 
1982         @Override
1983         public int[] getAcceptableTokens() {
1984             return new int[] {TokenTypes.METHOD_DEF};
1985         }
1986 
1987         @Override
1988         public int[] getRequiredTokens() {
1989             return new int[] {TokenTypes.METHOD_DEF};
1990         }
1991 
1992         // Locale.ENGLISH until #12104
1993         @Override
1994         public void visitToken(DetailAST ast) {
1995             if (ast.findFirstToken(TokenTypes.MODIFIERS).findFirstToken(
1996                     TokenTypes.BLOCK_COMMENT_BEGIN) == null) {
1997                 log(ast, "Incorrect AST structure.");
1998             }
1999             final int childCount = ast.getChildCount();
2000             if (childCount != METHOD_DEF_CHILD_COUNT) {
2001                 final String msg = String.format(Locale.ENGLISH,
2002                     "AST node in comment tree has wrong number of children. "
2003                             + "Expected is %d but was %d",
2004                     METHOD_DEF_CHILD_COUNT, childCount);
2005                 log(ast, msg);
2006             }
2007             // count children where comment lives
2008             int actualChildCount = 0;
2009             for (DetailAST child = ast.getFirstChild().getFirstChild(); child != null; child =
2010                     child.getNextSibling()) {
2011                 actualChildCount++;
2012             }
2013             final int cacheChildCount = ast.getFirstChild().getChildCount();
2014             if (cacheChildCount != actualChildCount) {
2015                 final String msg = String.format(Locale.ENGLISH,
2016                         "AST node with comment has wrong number of children. "
2017                                 + "Expected is %d but was %d",
2018                         cacheChildCount, actualChildCount);
2019                 log(ast, msg);
2020             }
2021         }
2022 
2023     }
2024 
2025     public static final class DummyFileSet extends AbstractFileSetCheck {
2026 
2027         private final List<String> methodCalls = new ArrayList<>();
2028 
2029         private boolean initCalled;
2030 
2031         @Override
2032         public void init() {
2033             super.init();
2034             initCalled = true;
2035         }
2036 
2037         @Override
2038         public void beginProcessing(String charset) {
2039             methodCalls.add("beginProcessing");
2040             super.beginProcessing(charset);
2041         }
2042 
2043         @Override
2044         public void finishProcessing() {
2045             methodCalls.add("finishProcessing");
2046             super.finishProcessing();
2047         }
2048 
2049         @Override
2050         protected void processFiltered(File file, FileText fileText) {
2051             methodCalls.add("processFiltered");
2052         }
2053 
2054         @Override
2055         public void destroy() {
2056             methodCalls.add("destroy");
2057             super.destroy();
2058         }
2059 
2060         public List<String> getMethodCalls() {
2061             return Collections.unmodifiableList(methodCalls);
2062         }
2063 
2064         public boolean isInitCalled() {
2065             return initCalled;
2066         }
2067 
2068         public MessageDispatcher getInternalMessageDispatcher() {
2069             return getMessageDispatcher();
2070         }
2071 
2072     }
2073 
2074 }