1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2019 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.puppycrawl.tools.checkstyle.Checker.EXCEPTION_MSG;
23  import static com.puppycrawl.tools.checkstyle.DefaultLogger.AUDIT_FINISHED_MESSAGE;
24  import static com.puppycrawl.tools.checkstyle.DefaultLogger.AUDIT_STARTED_MESSAGE;
25  import static com.puppycrawl.tools.checkstyle.checks.NewlineAtEndOfFileCheck.MSG_KEY_NO_NEWLINE_EOF;
26  import static org.hamcrest.CoreMatchers.instanceOf;
27  import static org.junit.Assert.assertArrayEquals;
28  import static org.junit.Assert.assertEquals;
29  import static org.junit.Assert.assertFalse;
30  import static org.junit.Assert.assertNotNull;
31  import static org.junit.Assert.assertNull;
32  import static org.junit.Assert.assertThat;
33  import static org.junit.Assert.assertTrue;
34  import static org.junit.Assert.fail;
35  
36  import java.io.ByteArrayInputStream;
37  import java.io.ByteArrayOutputStream;
38  import java.io.File;
39  import java.io.IOError;
40  import java.io.IOException;
41  import java.io.InputStream;
42  import java.io.InputStreamReader;
43  import java.io.LineNumberReader;
44  import java.io.UnsupportedEncodingException;
45  import java.lang.reflect.Field;
46  import java.lang.reflect.Method;
47  import java.nio.charset.StandardCharsets;
48  import java.nio.file.Files;
49  import java.util.ArrayList;
50  import java.util.Arrays;
51  import java.util.Collections;
52  import java.util.HashSet;
53  import java.util.List;
54  import java.util.Locale;
55  import java.util.Properties;
56  import java.util.Set;
57  import java.util.SortedSet;
58  import java.util.TreeSet;
59  import java.util.stream.Collectors;
60  
61  import org.junit.Rule;
62  import org.junit.Test;
63  import org.junit.rules.TemporaryFolder;
64  import org.powermock.reflect.Whitebox;
65  
66  import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
67  import com.puppycrawl.tools.checkstyle.api.AbstractFileSetCheck;
68  import com.puppycrawl.tools.checkstyle.api.AuditEvent;
69  import com.puppycrawl.tools.checkstyle.api.AuditListener;
70  import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
71  import com.puppycrawl.tools.checkstyle.api.Configuration;
72  import com.puppycrawl.tools.checkstyle.api.Context;
73  import com.puppycrawl.tools.checkstyle.api.DetailAST;
74  import com.puppycrawl.tools.checkstyle.api.ExternalResourceHolder;
75  import com.puppycrawl.tools.checkstyle.api.FileText;
76  import com.puppycrawl.tools.checkstyle.api.Filter;
77  import com.puppycrawl.tools.checkstyle.api.FilterSet;
78  import com.puppycrawl.tools.checkstyle.api.LocalizedMessage;
79  import com.puppycrawl.tools.checkstyle.api.MessageDispatcher;
80  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
81  import com.puppycrawl.tools.checkstyle.checks.NewlineAtEndOfFileCheck;
82  import com.puppycrawl.tools.checkstyle.checks.TranslationCheck;
83  import com.puppycrawl.tools.checkstyle.checks.coding.HiddenFieldCheck;
84  import com.puppycrawl.tools.checkstyle.filters.SuppressionFilter;
85  import com.puppycrawl.tools.checkstyle.internal.testmodules.DebugAuditAdapter;
86  import com.puppycrawl.tools.checkstyle.internal.testmodules.DebugFilter;
87  import com.puppycrawl.tools.checkstyle.internal.testmodules.TestBeforeExecutionFileFilter;
88  import com.puppycrawl.tools.checkstyle.internal.testmodules.TestFileSetCheck;
89  import com.puppycrawl.tools.checkstyle.internal.utils.CloseAndFlushTestByteArrayOutputStream;
90  import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
91  
92  public class CheckerTest extends AbstractModuleTestSupport {
93  
94      @Rule
95      public final TemporaryFolder temporaryFolder = new TemporaryFolder();
96  
97      private static Method getFireAuditFinished() throws NoSuchMethodException {
98          final Class<Checker> checkerClass = Checker.class;
99          final Method fireAuditFinished = checkerClass.getDeclaredMethod("fireAuditFinished");
100         fireAuditFinished.setAccessible(true);
101         return fireAuditFinished;
102     }
103 
104     private static Method getFireAuditStartedMethod() throws NoSuchMethodException {
105         final Class<Checker> checkerClass = Checker.class;
106         final Method fireAuditStarted = checkerClass.getDeclaredMethod("fireAuditStarted");
107         fireAuditStarted.setAccessible(true);
108         return fireAuditStarted;
109     }
110 
111     @Override
112     protected String getPackageLocation() {
113         return "com/puppycrawl/tools/checkstyle/checker";
114     }
115 
116     @Test
117     public void testDestroy() throws Exception {
118         final Checker checker = new Checker();
119         final DebugAuditAdapter auditAdapter = new DebugAuditAdapter();
120         checker.addListener(auditAdapter);
121         final TestFileSetCheck fileSet = new TestFileSetCheck();
122         checker.addFileSetCheck(fileSet);
123         final DebugFilter filter = new DebugFilter();
124         checker.addFilter(filter);
125         final TestBeforeExecutionFileFilter fileFilter = new TestBeforeExecutionFileFilter();
126         checker.addBeforeExecutionFileFilter(fileFilter);
127 
128         // should remove all listeners, file sets, and filters
129         checker.destroy();
130 
131         checker.process(Collections.singletonList(temporaryFolder.newFile()));
132         final SortedSet<LocalizedMessage> messages = new TreeSet<>();
133         messages.add(new LocalizedMessage(1, 0, "a Bundle", "message.key",
134                 new Object[] {"arg"}, null, getClass(), null));
135         checker.fireErrors("Some File Name", messages);
136 
137         assertFalse("Checker.destroy() doesn't remove listeners.", auditAdapter.wasCalled());
138         assertFalse("Checker.destroy() doesn't remove file sets.", fileSet.wasCalled());
139         assertFalse("Checker.destroy() doesn't remove filters.", filter.wasCalled());
140         assertFalse("Checker.destroy() doesn't remove file filters.", fileFilter.wasCalled());
141     }
142 
143     @Test
144     public void testAddListener() throws Exception {
145         final Checker checker = new Checker();
146         final DebugAuditAdapter auditAdapter = new DebugAuditAdapter();
147         checker.addListener(auditAdapter);
148 
149         // Let's try fire some events
150         getFireAuditStartedMethod().invoke(checker);
151         assertTrue("Checker.fireAuditStarted() doesn't call listener", auditAdapter.wasCalled());
152         assertTrue("Checker.fireAuditStarted() doesn't pass event", auditAdapter.wasEventPassed());
153 
154         auditAdapter.resetListener();
155         getFireAuditFinished().invoke(checker);
156         assertTrue("Checker.fireAuditFinished() doesn't call listener", auditAdapter.wasCalled());
157         assertTrue("Checker.fireAuditFinished() doesn't pass event", auditAdapter.wasEventPassed());
158 
159         auditAdapter.resetListener();
160         checker.fireFileStarted("Some File Name");
161         assertTrue("Checker.fireFileStarted() doesn't call listener", auditAdapter.wasCalled());
162         assertTrue("Checker.fireFileStarted() doesn't pass event", auditAdapter.wasEventPassed());
163 
164         auditAdapter.resetListener();
165         checker.fireFileFinished("Some File Name");
166         assertTrue("Checker.fireFileFinished() doesn't call listener", auditAdapter.wasCalled());
167         assertTrue("Checker.fireFileFinished() doesn't pass event", auditAdapter.wasEventPassed());
168 
169         auditAdapter.resetListener();
170         final SortedSet<LocalizedMessage> messages = new TreeSet<>();
171         messages.add(new LocalizedMessage(1, 0, "a Bundle", "message.key",
172                 new Object[] {"arg"}, null, getClass(), null));
173         checker.fireErrors("Some File Name", messages);
174         assertTrue("Checker.fireErrors() doesn't call listener", auditAdapter.wasCalled());
175         assertTrue("Checker.fireErrors() doesn't pass event", auditAdapter.wasEventPassed());
176     }
177 
178     @Test
179     public void testRemoveListener() throws Exception {
180         final Checker checker = new Checker();
181         final DebugAuditAdapter auditAdapter = new DebugAuditAdapter();
182         final DebugAuditAdapter aa2 = new DebugAuditAdapter();
183         checker.addListener(auditAdapter);
184         checker.addListener(aa2);
185         checker.removeListener(auditAdapter);
186 
187         // Let's try fire some events
188         getFireAuditStartedMethod().invoke(checker);
189         assertTrue("Checker.fireAuditStarted() doesn't call listener", aa2.wasCalled());
190         assertFalse("Checker.fireAuditStarted() does call removed listener",
191                 auditAdapter.wasCalled());
192 
193         aa2.resetListener();
194         getFireAuditFinished().invoke(checker);
195         assertTrue("Checker.fireAuditFinished() doesn't call listener", aa2.wasCalled());
196         assertFalse("Checker.fireAuditFinished() does call removed listener",
197                 auditAdapter.wasCalled());
198 
199         aa2.resetListener();
200         checker.fireFileStarted("Some File Name");
201         assertTrue("Checker.fireFileStarted() doesn't call listener", aa2.wasCalled());
202         assertFalse("Checker.fireFileStarted() does call removed listener",
203                 auditAdapter.wasCalled());
204 
205         aa2.resetListener();
206         checker.fireFileFinished("Some File Name");
207         assertTrue("Checker.fireFileFinished() doesn't call listener", aa2.wasCalled());
208         assertFalse("Checker.fireFileFinished() does call removed listener",
209                 auditAdapter.wasCalled());
210 
211         aa2.resetListener();
212         final SortedSet<LocalizedMessage> messages = new TreeSet<>();
213         messages.add(new LocalizedMessage(1, 0, "a Bundle", "message.key",
214                 new Object[] {"arg"}, null, getClass(), null));
215         checker.fireErrors("Some File Name", messages);
216         assertTrue("Checker.fireErrors() doesn't call listener", aa2.wasCalled());
217         assertFalse("Checker.fireErrors() does call removed listener", auditAdapter.wasCalled());
218     }
219 
220     @Test
221     public void testAddBeforeExecutionFileFilter() throws Exception {
222         final Checker checker = new Checker();
223         final TestBeforeExecutionFileFilter filter = new TestBeforeExecutionFileFilter();
224 
225         checker.addBeforeExecutionFileFilter(filter);
226 
227         filter.resetFilter();
228         checker.process(Collections.singletonList(new File("dummy.java")));
229         assertTrue("Checker.acceptFileStarted() doesn't call filter", filter.wasCalled());
230     }
231 
232     @Test
233     public void testRemoveBeforeExecutionFileFilter() throws Exception {
234         final Checker checker = new Checker();
235         final TestBeforeExecutionFileFilter filter = new TestBeforeExecutionFileFilter();
236         final TestBeforeExecutionFileFilter f2 = new TestBeforeExecutionFileFilter();
237         checker.addBeforeExecutionFileFilter(filter);
238         checker.addBeforeExecutionFileFilter(f2);
239         checker.removeBeforeExecutionFileFilter(filter);
240 
241         f2.resetFilter();
242         checker.process(Collections.singletonList(new File("dummy.java")));
243         assertTrue("Checker.acceptFileStarted() doesn't call filter", f2.wasCalled());
244         assertFalse("Checker.acceptFileStarted() does call removed filter", filter.wasCalled());
245     }
246 
247     @Test
248     public void testAddFilter() {
249         final Checker checker = new Checker();
250         final DebugFilter filter = new DebugFilter();
251 
252         checker.addFilter(filter);
253 
254         filter.resetFilter();
255         final SortedSet<LocalizedMessage> messages = new TreeSet<>();
256         messages.add(new LocalizedMessage(1, 0, "a Bundle", "message.key",
257                 new Object[] {"arg"}, null, getClass(), null));
258         checker.fireErrors("Some File Name", messages);
259         assertTrue("Checker.fireErrors() doesn't call filter", filter.wasCalled());
260     }
261 
262     @Test
263     public void testRemoveFilter() {
264         final Checker checker = new Checker();
265         final DebugFilter filter = new DebugFilter();
266         final DebugFilter f2 = new DebugFilter();
267         checker.addFilter(filter);
268         checker.addFilter(f2);
269         checker.removeFilter(filter);
270 
271         f2.resetFilter();
272         final SortedSet<LocalizedMessage> messages = new TreeSet<>();
273         messages.add(new LocalizedMessage(1, 0, "a Bundle", "message.key",
274                 new Object[] {"arg"}, null, getClass(), null));
275         checker.fireErrors("Some File Name", messages);
276         assertTrue("Checker.fireErrors() doesn't call filter", f2.wasCalled());
277         assertFalse("Checker.fireErrors() does call removed filter", filter.wasCalled());
278     }
279 
280     @Test
281     public void testFileExtensions() throws Exception {
282         final DefaultConfiguration checkerConfig = new DefaultConfiguration("configuration");
283         checkerConfig.addAttribute("charset", StandardCharsets.UTF_8.name());
284         checkerConfig.addAttribute("cacheFile", temporaryFolder.newFile().getPath());
285 
286         final Checker checker = new Checker();
287         checker.setModuleClassLoader(Thread.currentThread().getContextClassLoader());
288         checker.configure(checkerConfig);
289 
290         final DebugAuditAdapter auditAdapter = new DebugAuditAdapter();
291         checker.addListener(auditAdapter);
292 
293         final List<File> files = new ArrayList<>();
294         final File file = new File("file.pdf");
295         files.add(file);
296         final File otherFile = new File("file.java");
297         files.add(otherFile);
298         final String[] fileExtensions = {"java", "xml", "properties"};
299         checker.setFileExtensions(fileExtensions);
300         checker.setCacheFile(temporaryFolder.newFile().getPath());
301         final int counter = checker.process(files);
302 
303         // comparing to 1 as there is only one legal file in input
304         final int numLegalFiles = 1;
305         final PropertyCacheFile cache = Whitebox.getInternalState(checker, "cacheFile");
306         assertEquals("There were more legal files than expected",
307                 numLegalFiles, counter);
308         assertEquals("Audit was started on larger amount of files than expected",
309                 numLegalFiles, auditAdapter.getNumFilesStarted());
310         assertEquals("Audit was finished on larger amount of files than expected",
311                 numLegalFiles, auditAdapter.getNumFilesFinished());
312         assertNull("Cache shout not contain any file",
313                 cache.get(new File("file.java").getCanonicalPath()));
314     }
315 
316     @Test
317     public void testIgnoredFileExtensions() throws Exception {
318         final DefaultConfiguration checkerConfig = new DefaultConfiguration("configuration");
319         checkerConfig.addAttribute("charset", StandardCharsets.UTF_8.name());
320         checkerConfig.addAttribute("cacheFile", temporaryFolder.newFile().getPath());
321 
322         final Checker checker = new Checker();
323         checker.setModuleClassLoader(Thread.currentThread().getContextClassLoader());
324         checker.configure(checkerConfig);
325 
326         final DebugAuditAdapter auditAdapter = new DebugAuditAdapter();
327         checker.addListener(auditAdapter);
328 
329         final List<File> allIgnoredFiles = new ArrayList<>();
330         final File ignoredFile = new File("file.pdf");
331         allIgnoredFiles.add(ignoredFile);
332         final String[] fileExtensions = {"java", "xml", "properties"};
333         checker.setFileExtensions(fileExtensions);
334         checker.setCacheFile(temporaryFolder.newFile().getPath());
335         final int counter = checker.process(allIgnoredFiles);
336 
337         // comparing to 0 as there is no legal file in input
338         final int numLegalFiles = 0;
339         assertEquals("There were more legal files than expected",
340                 numLegalFiles, counter);
341         assertEquals("Audit was started on larger amount of files than expected",
342                 numLegalFiles, auditAdapter.getNumFilesStarted());
343         assertEquals("Audit was finished on larger amount of files than expected",
344                 numLegalFiles, auditAdapter.getNumFilesFinished());
345     }
346 
347     @Test
348     public void testSetters() {
349         // all  that is set by reflection, so just make code coverage be happy
350         final Checker checker = new Checker();
351         checker.setClassLoader(getClass().getClassLoader());
352         checker.setBasedir("some");
353         checker.setSeverity("ignore");
354 
355         final PackageObjectFactory factory = new PackageObjectFactory(
356             new HashSet<>(), Thread.currentThread().getContextClassLoader());
357         checker.setModuleFactory(factory);
358 
359         checker.setFileExtensions((String[]) null);
360         checker.setFileExtensions(".java", "xml");
361 
362         try {
363             checker.setCharset("UNKNOWN-CHARSET");
364             fail("Exception is expected");
365         }
366         catch (UnsupportedEncodingException ex) {
367             assertEquals("Error message is not expected",
368                     "unsupported charset: 'UNKNOWN-CHARSET'", ex.getMessage());
369         }
370     }
371 
372     @Test
373     public void testNoClassLoaderNoModuleFactory() {
374         final Checker checker = new Checker();
375 
376         try {
377             checker.finishLocalSetup();
378             fail("Exception is expected");
379         }
380         catch (CheckstyleException ex) {
381             assertEquals("Error message is not expected",
382                     "if no custom moduleFactory is set, moduleClassLoader must be specified",
383                     ex.getMessage());
384         }
385     }
386 
387     @Test
388     public void testNoModuleFactory() throws Exception {
389         final Checker checker = new Checker();
390         final ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
391 
392         checker.setModuleClassLoader(classLoader);
393         checker.finishLocalSetup();
394         final Context actualCtx = Whitebox.getInternalState(checker, "childContext");
395 
396         assertNotNull("Default module factory should be created when it is not specified",
397             actualCtx.get("moduleFactory"));
398         assertEquals("Invalid classLoader", classLoader, actualCtx.get("classLoader"));
399     }
400 
401     @Test
402     public void testFinishLocalSetupFullyInitialized() throws Exception {
403         final Checker checker = new Checker();
404         final ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
405         checker.setModuleClassLoader(contextClassLoader);
406         final PackageObjectFactory factory = new PackageObjectFactory(
407             new HashSet<>(), contextClassLoader);
408         checker.setModuleFactory(factory);
409         checker.setBasedir("testBaseDir");
410         checker.setLocaleLanguage("it");
411         checker.setLocaleCountry("IT");
412         checker.finishLocalSetup();
413 
414         final Context context = Whitebox.getInternalState(checker, "childContext");
415         assertEquals("Charset was different than expected",
416                 System.getProperty("file.encoding", StandardCharsets.UTF_8.name()),
417                 context.get("charset"));
418         assertEquals("Was used insufficient classloader",
419                 contextClassLoader, context.get("classLoader"));
420         assertEquals("Severity is set to unexpected value",
421                 "error", context.get("severity"));
422         assertEquals("Basedir is set to unexpected value",
423                 "testBaseDir", context.get("basedir"));
424 
425         final Field sLocale = LocalizedMessage.class.getDeclaredField("sLocale");
426         sLocale.setAccessible(true);
427         final Locale locale = (Locale) sLocale.get(null);
428         assertEquals("Locale is set to unexpected value", Locale.ITALY, locale);
429     }
430 
431     @Test
432     public void testSetupChildExceptions() {
433         final Checker checker = new Checker();
434         final PackageObjectFactory factory = new PackageObjectFactory(
435             new HashSet<>(), Thread.currentThread().getContextClassLoader());
436         checker.setModuleFactory(factory);
437 
438         final Configuration config = new DefaultConfiguration("java.lang.String");
439         try {
440             checker.setupChild(config);
441             fail("Exception is expected");
442         }
443         catch (CheckstyleException ex) {
444             assertEquals("Error message is not expected",
445                     "java.lang.String is not allowed as a child in Checker", ex.getMessage());
446         }
447     }
448 
449     @Test
450     public void testSetupChildInvalidProperty() throws Exception {
451         final DefaultConfiguration checkConfig = createModuleConfig(HiddenFieldCheck.class);
452         checkConfig.addAttribute("$$No such property", null);
453         try {
454             createChecker(checkConfig);
455             fail("Exception is expected");
456         }
457         catch (CheckstyleException ex) {
458             assertEquals("Error message is not expected",
459                     "cannot initialize module com.puppycrawl.tools.checkstyle.TreeWalker"
460                         + " - cannot initialize module " + checkConfig.getName()
461                         + " - Property '$$No such property'"
462                         + " does not exist, please check the documentation", ex.getMessage());
463         }
464     }
465 
466     @Test
467     public void testSetupChildListener() throws Exception {
468         final Checker checker = new Checker();
469         final PackageObjectFactory factory = new PackageObjectFactory(
470             new HashSet<>(), Thread.currentThread().getContextClassLoader());
471         checker.setModuleFactory(factory);
472 
473         final Configuration config = new DefaultConfiguration(
474             DebugAuditAdapter.class.getCanonicalName());
475         checker.setupChild(config);
476 
477         final List<AuditListener> listeners = Whitebox.getInternalState(checker, "listeners");
478         assertTrue("Invalid child listener class",
479             listeners.get(listeners.size() - 1) instanceof DebugAuditAdapter);
480     }
481 
482     @Test
483     public void testDestroyCheckerWithWrongCacheFileNameLength() throws Exception {
484         final Checker checker = new Checker();
485         final PackageObjectFactory factory = new PackageObjectFactory(
486             new HashSet<>(), Thread.currentThread().getContextClassLoader());
487         checker.setModuleFactory(factory);
488         checker.configure(new DefaultConfiguration("default config"));
489         // We set wrong file name length in order to reproduce IOException on OS Linux, OS Windows.
490         // The maximum file name length which is allowed in most UNIX, Windows file systems is 255.
491         // See https://en.wikipedia.org/wiki/Filename;
492         checker.setCacheFile(String.format(Locale.ENGLISH, "%0300d", 0));
493         try {
494             checker.destroy();
495             fail("Exception did not happen");
496         }
497         catch (IllegalStateException ex) {
498             assertTrue("Cause of exception differs from IOException",
499                     ex.getCause() instanceof IOException);
500         }
501     }
502 
503     /**
504      * It is OK to have long test method name here as it describes the test purpose.
505      */
506     @Test
507     public void testCacheAndCheckWhichDoesNotImplementExternalResourceHolderInterface()
508             throws Exception {
509         assertFalse("ExternalResourceHolder has changed his parent",
510                 ExternalResourceHolder.class.isAssignableFrom(HiddenFieldCheck.class));
511         final DefaultConfiguration checkConfig = createModuleConfig(HiddenFieldCheck.class);
512 
513         final DefaultConfiguration treeWalkerConfig = createModuleConfig(TreeWalker.class);
514         treeWalkerConfig.addChild(checkConfig);
515 
516         final DefaultConfiguration checkerConfig = createRootConfig(treeWalkerConfig);
517         checkerConfig.addAttribute("charset", StandardCharsets.UTF_8.name());
518 
519         final File cacheFile = temporaryFolder.newFile();
520         checkerConfig.addAttribute("cacheFile", cacheFile.getPath());
521 
522         final File tmpFile = temporaryFolder.newFile("file.java");
523         final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
524 
525         verify(checkerConfig, tmpFile.getPath(), expected);
526         final Properties cacheAfterFirstRun = new Properties();
527         cacheAfterFirstRun.load(Files.newBufferedReader(cacheFile.toPath()));
528 
529         // one more time to reuse cache
530         verify(checkerConfig, tmpFile.getPath(), expected);
531         final Properties cacheAfterSecondRun = new Properties();
532         cacheAfterSecondRun.load(Files.newBufferedReader(cacheFile.toPath()));
533 
534         assertEquals("Cache from first run differs from second run cache",
535                 cacheAfterFirstRun, cacheAfterSecondRun);
536     }
537 
538     @Test
539     public void testWithCacheWithNoViolation() throws Exception {
540         final Checker checker = new Checker();
541         final PackageObjectFactory factory = new PackageObjectFactory(
542             new HashSet<>(), Thread.currentThread().getContextClassLoader());
543         checker.setModuleFactory(factory);
544         checker.configure(createModuleConfig(TranslationCheck.class));
545 
546         final File cacheFile = temporaryFolder.newFile();
547         checker.setCacheFile(cacheFile.getPath());
548 
549         checker.setupChild(createModuleConfig(TranslationCheck.class));
550         final File tmpFile = temporaryFolder.newFile("file.java");
551         final List<File> files = new ArrayList<>(1);
552         files.add(tmpFile);
553         checker.process(files);
554 
555         // invoke destroy to persist cache
556         checker.destroy();
557 
558         final Properties cache = new Properties();
559         cache.load(Files.newBufferedReader(cacheFile.toPath()));
560 
561         // There should 2 objects in cache: processed file (file.java) and checker configuration.
562         final int expectedNumberOfObjectsInCache = 2;
563         assertEquals("Cache has unexpected size",
564                 expectedNumberOfObjectsInCache, cache.size());
565 
566         final String expectedConfigHash = "B8535A811CA90BE8B7A14D40BCA62B4FC2447B46";
567         assertEquals("Cache has unexpected hash",
568                 expectedConfigHash, cache.getProperty(PropertyCacheFile.CONFIG_HASH_KEY));
569 
570         assertNotNull("Cache file has null path",
571                 cache.getProperty(tmpFile.getPath()));
572     }
573 
574     @Test
575     public void testClearExistingCache() throws Exception {
576         final DefaultConfiguration checkerConfig = createRootConfig(null);
577         checkerConfig.addAttribute("charset", StandardCharsets.UTF_8.name());
578         final File cacheFile = temporaryFolder.newFile();
579         checkerConfig.addAttribute("cacheFile", cacheFile.getPath());
580 
581         final Checker checker = new Checker();
582         checker.setModuleClassLoader(Thread.currentThread().getContextClassLoader());
583         checker.configure(checkerConfig);
584         checker.addListener(getBriefUtLogger());
585 
586         checker.clearCache();
587         // invoke destroy to persist cache
588         checker.destroy();
589 
590         final Properties cacheAfterClear = new Properties();
591         cacheAfterClear.load(Files.newBufferedReader(cacheFile.toPath()));
592 
593         assertEquals("Cache has unexpected size",
594                 1, cacheAfterClear.size());
595         assertNotNull("Cache has null hash",
596                 cacheAfterClear.getProperty(PropertyCacheFile.CONFIG_HASH_KEY));
597 
598         final String pathToEmptyFile = temporaryFolder.newFile("file.java").getPath();
599         final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
600 
601         // file that should be audited is not in cache
602         verify(checker, pathToEmptyFile, pathToEmptyFile, expected);
603         final Properties cacheAfterSecondRun = new Properties();
604         cacheAfterSecondRun.load(Files.newBufferedReader(cacheFile.toPath()));
605 
606         assertNotNull("Cache has null path",
607                 cacheAfterSecondRun.getProperty(pathToEmptyFile));
608         assertEquals("Cash have changed it hash",
609             cacheAfterClear.getProperty(PropertyCacheFile.CONFIG_HASH_KEY),
610             cacheAfterSecondRun.getProperty(PropertyCacheFile.CONFIG_HASH_KEY)
611         );
612         final int expectedNumberOfObjectsInCacheAfterSecondRun = 2;
613         assertEquals("Cache has changed number of items",
614                 expectedNumberOfObjectsInCacheAfterSecondRun, cacheAfterSecondRun.size());
615     }
616 
617     @Test
618     public void testClearCache() throws Exception {
619         final DefaultConfiguration violationCheck =
620                 createModuleConfig(DummyFileSetViolationCheck.class);
621         final DefaultConfiguration checkerConfig = new DefaultConfiguration("myConfig");
622         checkerConfig.addAttribute("charset", "UTF-8");
623         final File cacheFile = temporaryFolder.newFile();
624         checkerConfig.addAttribute("cacheFile", cacheFile.getPath());
625         checkerConfig.addChild(violationCheck);
626         final Checker checker = new Checker();
627         checker.setModuleClassLoader(Thread.currentThread().getContextClassLoader());
628         checker.configure(checkerConfig);
629         checker.addListener(getBriefUtLogger());
630 
631         checker.process(Collections.singletonList(new File("dummy.java")));
632         checker.clearCache();
633         // invoke destroy to persist cache
634         final PropertyCacheFile cache = Whitebox.getInternalState(checker, "cacheFile");
635         cache.persist();
636 
637         final Properties cacheAfterClear = new Properties();
638         cacheAfterClear.load(Files.newBufferedReader(cacheFile.toPath()));
639 
640         assertEquals("Cache has unexpected size",
641                 1, cacheAfterClear.size());
642     }
643 
644     @Test
645     public void setFileExtension() {
646         final Checker checker = new Checker();
647         checker.setFileExtensions(".test1", "test2");
648         final String[] actual = Whitebox.getInternalState(checker, "fileExtensions");
649         assertArrayEquals("Extensions are not expected",
650                 new String[] {".test1", ".test2"}, actual);
651     }
652 
653     @Test
654     public void testClearCacheWhenCacheFileIsNotSet() {
655         // The idea of the test is to check that when cache file is not set,
656         // the invocation of clearCache method does not throw an exception.
657         final Checker checker = new Checker();
658         checker.clearCache();
659         assertNull("If cache file is not set the cache should default to null",
660             Whitebox.getInternalState(checker, "cacheFile"));
661     }
662 
663     /**
664      * Test doesn't need to be serialized.
665      * @noinspection SerializableInnerClassWithNonSerializableOuterClass
666      */
667     @Test
668     public void testCatchErrorInProcessFilesMethod() throws Exception {
669         // Assume that I/O error is happened when we try to invoke 'lastModified()' method.
670         final String errorMessage = "Java Virtual Machine is broken"
671             + " or has run out of resources necessary for it to continue operating.";
672         final Error expectedError = new IOError(new InternalError(errorMessage));
673 
674         final File mock = new File("testFile") {
675             private static final long serialVersionUID = 1L;
676 
677             /**
678              * Test is checking catch clause when exception is thrown.
679              * @noinspection ProhibitedExceptionThrown
680              */
681             @Override
682             public long lastModified() {
683                 throw expectedError;
684             }
685         };
686 
687         final Checker checker = new Checker();
688         final List<File> filesToProcess = new ArrayList<>();
689         filesToProcess.add(mock);
690         try {
691             checker.process(filesToProcess);
692             fail("IOError is expected!");
693         }
694         // -@cs[IllegalCatchExtended] Testing for catch Error is part of 100% coverage.
695         catch (Error error) {
696             assertThat("Error cause differs from IOError",
697                     error.getCause(), instanceOf(IOError.class));
698             assertThat("Error cause is not InternalError",
699                     error.getCause().getCause(), instanceOf(InternalError.class));
700             assertEquals("Error message is not expected",
701                     errorMessage, error.getCause().getCause().getMessage());
702         }
703     }
704 
705     /**
706      * Test doesn't need to be serialized.
707      * @noinspection SerializableInnerClassWithNonSerializableOuterClass
708      */
709     @Test
710     public void testCatchErrorWithNoFileName() throws Exception {
711         // Assume that I/O error is happened when we try to invoke 'lastModified()' method.
712         final String errorMessage = "Java Virtual Machine is broken"
713             + " or has run out of resources necessary for it to continue operating.";
714         final Error expectedError = new IOError(new InternalError(errorMessage));
715 
716         final File mock = new File("testFile") {
717             private static final long serialVersionUID = 1L;
718 
719             /**
720              * Test is checking catch clause when exception is thrown.
721              * @noinspection ProhibitedExceptionThrown
722              */
723             @Override
724             public long lastModified() {
725                 throw expectedError;
726             }
727 
728             @Override
729             public String getAbsolutePath() {
730                 return null;
731             }
732         };
733 
734         final Checker checker = new Checker();
735         final List<File> filesToProcess = new ArrayList<>();
736         filesToProcess.add(mock);
737         try {
738             checker.process(filesToProcess);
739             fail("IOError is expected!");
740         }
741         // -@cs[IllegalCatchExtended] Testing for catch Error is part of 100% coverage.
742         catch (Error error) {
743             assertThat("Error cause differs from IOError",
744                     error.getCause(), instanceOf(IOError.class));
745             assertThat("Error cause is not InternalError",
746                     error.getCause().getCause(), instanceOf(InternalError.class));
747             assertEquals("Error message is not expected",
748                     errorMessage, error.getCause().getCause().getMessage());
749         }
750     }
751 
752     /**
753      * It is OK to have long test method name here as it describes the test purpose.
754      */
755     @Test
756     public void testCacheAndFilterWhichDoesNotImplementExternalResourceHolderInterface()
757             throws Exception {
758         assertFalse("ExternalResourceHolder has changed its parent",
759                 ExternalResourceHolder.class.isAssignableFrom(DummyFilter.class));
760         final DefaultConfiguration filterConfig = createModuleConfig(DummyFilter.class);
761 
762         final DefaultConfiguration checkerConfig = createRootConfig(filterConfig);
763         final File cacheFile = temporaryFolder.newFile();
764         checkerConfig.addAttribute("cacheFile", cacheFile.getPath());
765 
766         final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
767         final String pathToEmptyFile = temporaryFolder.newFile("file.java").getPath();
768 
769         verify(checkerConfig, pathToEmptyFile, expected);
770         final Properties cacheAfterFirstRun = new Properties();
771         cacheAfterFirstRun.load(Files.newBufferedReader(cacheFile.toPath()));
772 
773         // One more time to use cache.
774         verify(checkerConfig, pathToEmptyFile, expected);
775         final Properties cacheAfterSecondRun = new Properties();
776         cacheAfterSecondRun.load(Files.newBufferedReader(cacheFile.toPath()));
777 
778         assertEquals(
779                 "Cache file has changed its path",
780             cacheAfterFirstRun.getProperty(pathToEmptyFile),
781             cacheAfterSecondRun.getProperty(pathToEmptyFile)
782         );
783         assertEquals(
784                 "Cache has changed its hash",
785             cacheAfterFirstRun.getProperty(PropertyCacheFile.CONFIG_HASH_KEY),
786             cacheAfterSecondRun.getProperty(PropertyCacheFile.CONFIG_HASH_KEY)
787         );
788         final int expectedNumberOfObjectsInCache = 2;
789         assertEquals("Number of items in cache differs from expected",
790                 expectedNumberOfObjectsInCache, cacheAfterFirstRun.size());
791         assertEquals("Number of items in cache differs from expected",
792                 expectedNumberOfObjectsInCache, cacheAfterSecondRun.size());
793     }
794 
795     /**
796      * It is OK to have long test method name here as it describes the test purpose.
797      */
798     // -@cs[ExecutableStatementCount] This test needs to verify many things.
799     @Test
800     public void testCacheAndCheckWhichAddsNewResourceLocationButKeepsSameCheckerInstance()
801             throws Exception {
802         // Use case (https://github.com/checkstyle/checkstyle/pull/3092#issuecomment-218162436):
803         // Imagine that cache exists in a file. New version of Checkstyle appear.
804         // New release contains update to a some check to have additional external resource.
805         // User update his configuration and run validation as usually.
806         // Cache should not be reused.
807 
808         final DynamicalResourceHolderCheck check = new DynamicalResourceHolderCheck();
809         final String firstExternalResourceLocation = getPath("InputCheckerImportControlOne.xml");
810         final String firstExternalResourceKey = PropertyCacheFile.EXTERNAL_RESOURCE_KEY_PREFIX
811                 + firstExternalResourceLocation;
812         check.setFirstExternalResourceLocation(firstExternalResourceLocation);
813 
814         final DefaultConfiguration checkerConfig = createRootConfig(null);
815         final File cacheFile = temporaryFolder.newFile();
816         checkerConfig.addAttribute("cacheFile", cacheFile.getPath());
817 
818         final Checker checker = new Checker();
819         checker.setModuleClassLoader(Thread.currentThread().getContextClassLoader());
820         checker.addFileSetCheck(check);
821         checker.addFilter(new DummyFilterSet());
822         checker.configure(checkerConfig);
823         checker.addListener(getBriefUtLogger());
824 
825         final String pathToEmptyFile = temporaryFolder.newFile("file.java").getPath();
826         final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
827 
828         verify(checker, pathToEmptyFile, expected);
829         final Properties cacheAfterFirstRun = new Properties();
830         cacheAfterFirstRun.load(Files.newBufferedReader(cacheFile.toPath()));
831 
832         final int expectedNumberOfObjectsInCacheAfterFirstRun = 4;
833         assertEquals("Number of items in cache differs from expected",
834                 expectedNumberOfObjectsInCacheAfterFirstRun, cacheAfterFirstRun.size());
835 
836         // Change a list of external resources which are used by the check
837         final String secondExternalResourceLocation = "InputCheckerImportControlTwo.xml";
838         final String secondExternalResourceKey = PropertyCacheFile.EXTERNAL_RESOURCE_KEY_PREFIX
839                 + secondExternalResourceLocation;
840         check.setSecondExternalResourceLocation(secondExternalResourceLocation);
841 
842         checker.addFileSetCheck(check);
843         checker.configure(checkerConfig);
844 
845         verify(checker, pathToEmptyFile, expected);
846         final Properties cacheAfterSecondRun = new Properties();
847         cacheAfterSecondRun.load(Files.newBufferedReader(cacheFile.toPath()));
848 
849         assertEquals("Cache file has changed its path",
850             cacheAfterFirstRun.getProperty(pathToEmptyFile),
851             cacheAfterSecondRun.getProperty(pathToEmptyFile)
852         );
853         assertEquals(
854                 "Cache has changed its hash",
855             cacheAfterFirstRun.getProperty(PropertyCacheFile.CONFIG_HASH_KEY),
856             cacheAfterSecondRun.getProperty(PropertyCacheFile.CONFIG_HASH_KEY)
857         );
858         assertEquals("Cache has changed its resource key",
859             cacheAfterFirstRun.getProperty(firstExternalResourceKey),
860             cacheAfterSecondRun.getProperty(firstExternalResourceKey)
861         );
862         assertNotNull("Cache has null as a resource key",
863                 cacheAfterFirstRun.getProperty(firstExternalResourceKey));
864         final int expectedNumberOfObjectsInCacheAfterSecondRun = 4;
865         assertEquals("Number of items in cache differs from expected",
866                 expectedNumberOfObjectsInCacheAfterSecondRun, cacheAfterSecondRun.size());
867         assertNull("Cache has not null as a resource key",
868                 cacheAfterFirstRun.getProperty(secondExternalResourceKey));
869         assertNotNull("Cache has null as a resource key",
870                 cacheAfterSecondRun.getProperty(secondExternalResourceKey));
871     }
872 
873     @Test
874     public void testClearLazyLoadCacheInDetailAST() throws Exception {
875         final DefaultConfiguration checkConfig1 =
876             createModuleConfig(CheckWhichDoesNotRequireCommentNodes.class);
877         final DefaultConfiguration checkConfig2 =
878             createModuleConfig(CheckWhichRequiresCommentNodes.class);
879 
880         final DefaultConfiguration treeWalkerConfig = createModuleConfig(TreeWalker.class);
881         treeWalkerConfig.addChild(checkConfig1);
882         treeWalkerConfig.addChild(checkConfig2);
883 
884         final DefaultConfiguration checkerConfig = createRootConfig(treeWalkerConfig);
885 
886         final String filePath = getPath("InputCheckerClearDetailAstLazyLoadCache.java");
887         final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
888 
889         verify(checkerConfig, filePath, expected);
890     }
891 
892     @Test
893     public void testCacheOnViolationSuppression() throws Exception {
894         final File cacheFile = temporaryFolder.newFile();
895         final DefaultConfiguration violationCheck =
896                 createModuleConfig(DummyFileSetViolationCheck.class);
897 
898         final DefaultConfiguration filterConfig = createModuleConfig(SuppressionFilter.class);
899         filterConfig.addAttribute("file", getPath("InputCheckerSuppressAll.xml"));
900 
901         final DefaultConfiguration checkerConfig = createRootConfig(violationCheck);
902         checkerConfig.addAttribute("cacheFile", cacheFile.getPath());
903         checkerConfig.addChild(filterConfig);
904 
905         final String fileViolationPath = temporaryFolder.newFile("ViolationFile.java").getPath();
906         final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
907 
908         verify(checkerConfig, fileViolationPath, expected);
909 
910         try (InputStream input = Files.newInputStream(cacheFile.toPath())) {
911             final Properties details = new Properties();
912             details.load(input);
913 
914             assertNotNull("suppressed violation file saved in cache",
915                     details.getProperty(fileViolationPath));
916         }
917     }
918 
919     @Test
920     public void testHaltOnException() throws Exception {
921         final DefaultConfiguration checkConfig =
922             createModuleConfig(CheckWhichThrowsError.class);
923         final String filePath = getPath("InputChecker.java");
924         try {
925             verify(checkConfig, filePath);
926             fail("Exception is expected");
927         }
928         catch (CheckstyleException ex) {
929             assertEquals("Error message is not expected",
930                     "Exception was thrown while processing " + filePath, ex.getMessage());
931         }
932     }
933 
934     @Test
935     public void testExceptionWithCache() throws Exception {
936         final File cacheFile = temporaryFolder.newFile();
937 
938         final DefaultConfiguration checkConfig =
939                 createModuleConfig(CheckWhichThrowsError.class);
940 
941         final DefaultConfiguration treewalkerConfig =
942                 createModuleConfig(TreeWalker.class);
943         treewalkerConfig.addChild(checkConfig);
944 
945         final DefaultConfiguration checkerConfig = createRootConfig(treewalkerConfig);
946         checkerConfig.addAttribute("charset", StandardCharsets.UTF_8.name());
947         checkerConfig.addAttribute("cacheFile", cacheFile.getPath());
948         checkerConfig.addChild(treewalkerConfig);
949 
950         final Checker checker = createChecker(checkerConfig);
951 
952         final String filePath = getPath("InputChecker.java");
953         try {
954             checker.process(Collections.singletonList(new File(filePath)));
955             fail("Exception is expected");
956         }
957         catch (CheckstyleException ex) {
958             assertEquals("Error message is not expected",
959                     "Exception was thrown while processing " + filePath, ex.getMessage());
960 
961             // destroy is called by Main
962             checker.destroy();
963 
964             final Properties cache = new Properties();
965             cache.load(Files.newBufferedReader(cacheFile.toPath()));
966 
967             assertEquals("Cache has unexpected size",
968                     1, cache.size());
969             assertNull("testFile is not in cache",
970                     cache.getProperty(filePath));
971         }
972     }
973 
974     /**
975      * Test doesn't need to be serialized.
976      * @noinspection SerializableInnerClassWithNonSerializableOuterClass
977      */
978     @Test
979     public void testCatchErrorWithCache() throws Exception {
980         final File cacheFile = temporaryFolder.newFile();
981 
982         final DefaultConfiguration checkerConfig = new DefaultConfiguration("configuration");
983         checkerConfig.addAttribute("charset", StandardCharsets.UTF_8.name());
984         checkerConfig.addAttribute("cacheFile", cacheFile.getPath());
985 
986         final String errorMessage = "Java Virtual Machine is broken"
987             + " or has run out of resources necessary for it to continue operating.";
988         final Error expectedError = new IOError(new InternalError(errorMessage));
989 
990         final File mock = new File("testFile") {
991             private static final long serialVersionUID = 1L;
992 
993             @Override
994             public String getAbsolutePath() {
995                 return "testFile";
996             }
997 
998             /**
999              * Test is checking catch clause when exception is thrown.
1000              * @noinspection ProhibitedExceptionThrown
1001              */
1002             @Override
1003             public File getAbsoluteFile() {
1004                 throw expectedError;
1005             }
1006         };
1007 
1008         final Checker checker = new Checker();
1009         checker.setModuleClassLoader(Thread.currentThread().getContextClassLoader());
1010         checker.configure(checkerConfig);
1011         final List<File> filesToProcess = new ArrayList<>();
1012         filesToProcess.add(mock);
1013         try {
1014             checker.process(filesToProcess);
1015             fail("IOError is expected!");
1016         }
1017         // -@cs[IllegalCatchExtended] Testing for catch Error is part of 100% coverage.
1018         catch (Error error) {
1019             assertThat("Error cause differs from IOError",
1020                     error.getCause(), instanceOf(IOError.class));
1021             assertEquals("Error message is not expected",
1022                     errorMessage, error.getCause().getCause().getMessage());
1023 
1024             // destroy is called by Main
1025             checker.destroy();
1026 
1027             final Properties cache = new Properties();
1028             cache.load(Files.newBufferedReader(cacheFile.toPath()));
1029 
1030             assertEquals("Cache has unexpected size",
1031                     1, cache.size());
1032             assertNull("testFile is not in cache",
1033                     cache.getProperty("testFile"));
1034         }
1035     }
1036 
1037     /**
1038      * Test doesn't need to be serialized.
1039      * @noinspection SerializableInnerClassWithNonSerializableOuterClass
1040      */
1041     @Test
1042     public void testCatchErrorWithCacheWithNoFileName() throws Exception {
1043         final File cacheFile = temporaryFolder.newFile();
1044 
1045         final DefaultConfiguration checkerConfig = new DefaultConfiguration("configuration");
1046         checkerConfig.addAttribute("charset", StandardCharsets.UTF_8.name());
1047         checkerConfig.addAttribute("cacheFile", cacheFile.getPath());
1048 
1049         final String errorMessage = "Java Virtual Machine is broken"
1050             + " or has run out of resources necessary for it to continue operating.";
1051         final Error expectedError = new IOError(new InternalError(errorMessage));
1052 
1053         final File mock = new File("testFile") {
1054             private static final long serialVersionUID = 1L;
1055 
1056             /**
1057              * Test is checking catch clause when exception is thrown.
1058              * @noinspection ProhibitedExceptionThrown
1059              */
1060             @Override
1061             public String getAbsolutePath() {
1062                 throw expectedError;
1063             }
1064         };
1065 
1066         final Checker checker = new Checker();
1067         checker.setModuleClassLoader(Thread.currentThread().getContextClassLoader());
1068         checker.configure(checkerConfig);
1069         final List<File> filesToProcess = new ArrayList<>();
1070         filesToProcess.add(mock);
1071         try {
1072             checker.process(filesToProcess);
1073             fail("IOError is expected!");
1074         }
1075         // -@cs[IllegalCatchExtended] Testing for catch Error is part of 100% coverage.
1076         catch (Error error) {
1077             assertThat("Error cause differs from IOError",
1078                     error.getCause(), instanceOf(IOError.class));
1079             assertEquals("Error message is not expected",
1080                     errorMessage, error.getCause().getCause().getMessage());
1081 
1082             // destroy is called by Main
1083             checker.destroy();
1084 
1085             final Properties cache = new Properties();
1086             cache.load(Files.newBufferedReader(cacheFile.toPath()));
1087 
1088             assertEquals("Cache has unexpected size",
1089                     1, cache.size());
1090         }
1091     }
1092 
1093     /**
1094      * Test doesn't need to be serialized.
1095      * @noinspection SerializableInnerClassWithNonSerializableOuterClass
1096      */
1097     @Test
1098     public void testExceptionWithNoFileName() {
1099         final String errorMessage = "Security Exception";
1100         final RuntimeException expectedError = new SecurityException(errorMessage);
1101 
1102         final File mock = new File("testFile") {
1103             private static final long serialVersionUID = 1L;
1104 
1105             /**
1106              * Test is checking catch clause when exception is thrown.
1107              * @noinspection ProhibitedExceptionThrown
1108              */
1109             @Override
1110             public String getAbsolutePath() {
1111                 throw expectedError;
1112             }
1113         };
1114 
1115         final Checker checker = new Checker();
1116         final List<File> filesToProcess = new ArrayList<>();
1117         filesToProcess.add(mock);
1118         try {
1119             checker.process(filesToProcess);
1120             fail("SecurityException is expected!");
1121         }
1122         catch (CheckstyleException ex) {
1123             assertThat("Error cause differs from SecurityException",
1124                     ex.getCause(), instanceOf(SecurityException.class));
1125             assertEquals("Error message is not expected",
1126                     errorMessage, ex.getCause().getMessage());
1127         }
1128     }
1129 
1130     /**
1131      * Test doesn't need to be serialized.
1132      * @noinspection SerializableInnerClassWithNonSerializableOuterClass
1133      */
1134     @Test
1135     public void testExceptionWithCacheAndNoFileName() throws Exception {
1136         final File cacheFile = temporaryFolder.newFile();
1137 
1138         final DefaultConfiguration checkerConfig = new DefaultConfiguration("configuration");
1139         checkerConfig.addAttribute("charset", StandardCharsets.UTF_8.name());
1140         checkerConfig.addAttribute("cacheFile", cacheFile.getPath());
1141 
1142         final String errorMessage = "Security Exception";
1143         final RuntimeException expectedError = new SecurityException(errorMessage);
1144 
1145         final File mock = new File("testFile") {
1146             private static final long serialVersionUID = 1L;
1147 
1148             /**
1149              * Test is checking catch clause when exception is thrown.
1150              * @noinspection ProhibitedExceptionThrown
1151              */
1152             @Override
1153             public String getAbsolutePath() {
1154                 throw expectedError;
1155             }
1156         };
1157 
1158         final Checker checker = new Checker();
1159         checker.setModuleClassLoader(Thread.currentThread().getContextClassLoader());
1160         checker.configure(checkerConfig);
1161         final List<File> filesToProcess = new ArrayList<>();
1162         filesToProcess.add(mock);
1163         try {
1164             checker.process(filesToProcess);
1165             fail("SecurityException is expected!");
1166         }
1167         catch (CheckstyleException ex) {
1168             assertThat("Error cause differs from SecurityException",
1169                     ex.getCause(), instanceOf(SecurityException.class));
1170             assertEquals("Error message is not expected",
1171                     errorMessage, ex.getCause().getMessage());
1172 
1173             // destroy is called by Main
1174             checker.destroy();
1175 
1176             final Properties cache = new Properties();
1177             cache.load(Files.newBufferedReader(cacheFile.toPath()));
1178 
1179             assertEquals("Cache has unexpected size",
1180                     1, cache.size());
1181         }
1182     }
1183 
1184     @Test
1185     public void testHaltOnExceptionOff() throws Exception {
1186         final DefaultConfiguration checkConfig =
1187             createModuleConfig(CheckWhichThrowsError.class);
1188 
1189         final DefaultConfiguration treeWalkerConfig = createModuleConfig(TreeWalker.class);
1190         treeWalkerConfig.addChild(checkConfig);
1191 
1192         final DefaultConfiguration checkerConfig = createRootConfig(treeWalkerConfig);
1193         checkerConfig.addChild(treeWalkerConfig);
1194 
1195         checkerConfig.addAttribute("haltOnException", "false");
1196 
1197         final String filePath = getPath("InputChecker.java");
1198         final String[] expected = {
1199             "1: " + getCheckMessage(EXCEPTION_MSG, "java.lang.IndexOutOfBoundsException: test"),
1200         };
1201 
1202         verify(checkerConfig, filePath, expected);
1203     }
1204 
1205     @Test
1206     public void testTabViolationDefault() throws Exception {
1207         final DefaultConfiguration checkConfig =
1208             createModuleConfig(VerifyPositionAfterTabFileSet.class);
1209         final String[] expected = {
1210             "2:9: violation",
1211             "3:17: violation",
1212         };
1213         verify(checkConfig, getPath("InputCheckerTabCharacter.txt"),
1214             expected);
1215     }
1216 
1217     @Test
1218     public void testTabViolation() throws Exception {
1219         final DefaultConfiguration checkConfig =
1220             createModuleConfig(VerifyPositionAfterTabFileSet.class);
1221         final DefaultConfiguration checkerConfig = createRootConfig(checkConfig);
1222         checkerConfig.addAttribute("tabWidth", "4");
1223         final String[] expected = {
1224             "2:5: violation",
1225             "3:9: violation",
1226         };
1227         verify(checkerConfig, getPath("InputCheckerTabCharacter.txt"),
1228             expected);
1229     }
1230 
1231     @Test
1232     public void testCheckerProcessCallAllNeededMethodsOfFileSets() throws Exception {
1233         final DummyFileSet fileSet = new DummyFileSet();
1234         final Checker checker = new Checker();
1235         checker.addFileSetCheck(fileSet);
1236         checker.process(Collections.singletonList(new File("dummy.java")));
1237         final List<String> expected =
1238             Arrays.asList("beginProcessing", "finishProcessing", "destroy");
1239         assertArrayEquals("Method calls were not expected",
1240                 expected.toArray(), fileSet.getMethodCalls().toArray());
1241     }
1242 
1243     @Test
1244     public void testSetFileSetCheckSetsMessageDispatcher() {
1245         final DummyFileSet fileSet = new DummyFileSet();
1246         final Checker checker = new Checker();
1247         checker.addFileSetCheck(fileSet);
1248         assertEquals("Message dispatcher was not expected",
1249                 checker, fileSet.getInternalMessageDispatcher());
1250     }
1251 
1252     @Test
1253     public void testAddAuditListenerAsChild() throws Exception {
1254         final Checker checker = new Checker();
1255         final DebugAuditAdapter auditAdapter = new DebugAuditAdapter();
1256         final PackageObjectFactory factory = new PackageObjectFactory(
1257                 new HashSet<>(), Thread.currentThread().getContextClassLoader()) {
1258             @Override
1259             public Object createModule(String name) throws CheckstyleException {
1260                 Object adapter = auditAdapter;
1261                 if (!name.equals(DebugAuditAdapter.class.getName())) {
1262                     adapter = super.createModule(name);
1263                 }
1264                 return adapter;
1265             }
1266         };
1267         checker.setModuleFactory(factory);
1268         checker.setupChild(createModuleConfig(DebugAuditAdapter.class));
1269         // Let's try fire some events
1270         checker.process(Collections.singletonList(new File("dummy.java")));
1271         assertTrue("Checker.fireAuditStarted() doesn't call listener", auditAdapter.wasCalled());
1272     }
1273 
1274     @Test
1275     public void testAddBeforeExecutionFileFilterAsChild() throws Exception {
1276         final Checker checker = new Checker();
1277         final TestBeforeExecutionFileFilter fileFilter = new TestBeforeExecutionFileFilter();
1278         final PackageObjectFactory factory = new PackageObjectFactory(
1279                 new HashSet<>(), Thread.currentThread().getContextClassLoader()) {
1280             @Override
1281             public Object createModule(String name) throws CheckstyleException {
1282                 Object filter = fileFilter;
1283                 if (!name.equals(TestBeforeExecutionFileFilter.class.getName())) {
1284                     filter = super.createModule(name);
1285                 }
1286                 return filter;
1287             }
1288         };
1289         checker.setModuleFactory(factory);
1290         checker.setupChild(createModuleConfig(TestBeforeExecutionFileFilter.class));
1291         checker.process(Collections.singletonList(new File("dummy.java")));
1292         assertTrue("Checker.acceptFileStarted() doesn't call listener", fileFilter.wasCalled());
1293     }
1294 
1295     @Test
1296     public void testFileSetCheckInitWhenAddedAsChild() throws Exception {
1297         final Checker checker = new Checker();
1298         final DummyFileSet fileSet = new DummyFileSet();
1299         final PackageObjectFactory factory = new PackageObjectFactory(
1300                 new HashSet<>(), Thread.currentThread().getContextClassLoader()) {
1301             @Override
1302             public Object createModule(String name) throws CheckstyleException {
1303                 Object check = fileSet;
1304                 if (!name.equals(DummyFileSet.class.getName())) {
1305                     check = super.createModule(name);
1306                 }
1307                 return check;
1308             }
1309         };
1310         checker.setModuleFactory(factory);
1311         checker.finishLocalSetup();
1312         checker.setupChild(createModuleConfig(DummyFileSet.class));
1313         assertTrue("FileSetCheck.init() wasn't called", fileSet.isInitCalled());
1314     }
1315 
1316     // -@cs[CheckstyleTestMakeup] must use raw class to directly initialize DefaultLogger
1317     @Test
1318     public void testDefaultLoggerClosesItStreams() throws Exception {
1319         final Checker checker = new Checker();
1320         try (CloseAndFlushTestByteArrayOutputStream testInfoOutputStream =
1321                 new CloseAndFlushTestByteArrayOutputStream();
1322             CloseAndFlushTestByteArrayOutputStream testErrorOutputStream =
1323                 new CloseAndFlushTestByteArrayOutputStream()) {
1324             checker.setModuleClassLoader(Thread.currentThread().getContextClassLoader());
1325             checker.addListener(new DefaultLogger(testInfoOutputStream,
1326                 true, testErrorOutputStream, true));
1327 
1328             final File tmpFile = temporaryFolder.newFile("file.java");
1329             final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
1330 
1331             verify(checker, tmpFile.getPath(), expected);
1332 
1333             assertEquals("Close count was not expected",
1334                     1, testInfoOutputStream.getCloseCount());
1335             assertEquals("Flush count was not expected",
1336                     3, testInfoOutputStream.getFlushCount());
1337             assertEquals("Close count was not expected",
1338                     1, testErrorOutputStream.getCloseCount());
1339             assertEquals("Flush count was not expected",
1340                     1, testErrorOutputStream.getFlushCount());
1341         }
1342     }
1343 
1344     // -@cs[CheckstyleTestMakeup] must use raw class to directly initialize DefaultLogger
1345     @Test
1346     public void testXmlLoggerClosesItStreams() throws Exception {
1347         final Checker checker = new Checker();
1348         try (CloseAndFlushTestByteArrayOutputStream testInfoOutputStream =
1349                 new CloseAndFlushTestByteArrayOutputStream()) {
1350             checker.setModuleClassLoader(Thread.currentThread().getContextClassLoader());
1351             checker.addListener(new XMLLogger(testInfoOutputStream, true));
1352 
1353             final File tmpFile = temporaryFolder.newFile("file.java");
1354             final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
1355 
1356             verify(checker, tmpFile.getPath(), tmpFile.getPath(), expected);
1357 
1358             assertEquals("Close count was not expected",
1359                     1, testInfoOutputStream.getCloseCount());
1360             assertEquals("Flush count was not expected",
1361                     0, testInfoOutputStream.getFlushCount());
1362         }
1363     }
1364 
1365     @Test
1366     public void testDuplicatedModule() throws Exception {
1367         // we need to test a module with two instances, one with id and the other not
1368         final DefaultConfiguration moduleConfig1 =
1369                 createModuleConfig(NewlineAtEndOfFileCheck.class);
1370         final DefaultConfiguration moduleConfig2 =
1371                 createModuleConfig(NewlineAtEndOfFileCheck.class);
1372         moduleConfig2.addAttribute("id", "ModuleId");
1373         final DefaultConfiguration root = new DefaultConfiguration("root");
1374         root.addChild(moduleConfig1);
1375         root.addChild(moduleConfig2);
1376         final Checker checker = new Checker();
1377         checker.setModuleClassLoader(Thread.currentThread().getContextClassLoader());
1378         checker.configure(root);
1379         // BriefUtLogger does not print the module name or id postfix,
1380         // so we need to set logger manually
1381         final ByteArrayOutputStream out = Whitebox.getInternalState(this, "stream");
1382         final DefaultLogger logger =
1383                 new DefaultLogger(out, true, out, false, new AuditEventDefaultFormatter());
1384         checker.addListener(logger);
1385 
1386         final String path = temporaryFolder.newFile("file.java").getPath();
1387         final String errorMessage =
1388                 getCheckMessage(NewlineAtEndOfFileCheck.class, MSG_KEY_NO_NEWLINE_EOF);
1389         final String[] expected = {
1390             "1: " + errorMessage + " [NewlineAtEndOfFile]",
1391             "1: " + errorMessage + " [ModuleId]",
1392         };
1393 
1394         // super.verify does not work here, for we change the logger
1395         out.flush();
1396         final int errs = checker.process(Collections.singletonList(new File(path)));
1397         try (ByteArrayInputStream inputStream =
1398                 new ByteArrayInputStream(out.toByteArray());
1399             LineNumberReader lnr = new LineNumberReader(
1400                 new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
1401             // we need to ignore the unrelated lines
1402             final List<String> actual = lnr.lines()
1403                     .filter(line -> !getCheckMessage(AUDIT_STARTED_MESSAGE).equals(line))
1404                     .filter(line -> !getCheckMessage(AUDIT_FINISHED_MESSAGE).equals(line))
1405                     .limit(expected.length)
1406                     .sorted()
1407                     .collect(Collectors.toList());
1408             Arrays.sort(expected);
1409 
1410             for (int i = 0; i < expected.length; i++) {
1411                 final String expectedResult = "[ERROR] " + path + ":" + expected[i];
1412                 assertEquals("error message " + i, expectedResult, actual.get(i));
1413             }
1414 
1415             assertEquals("unexpected output: " + lnr.readLine(), expected.length, errs);
1416         }
1417 
1418         checker.destroy();
1419     }
1420 
1421     private static class DummyFilter implements Filter {
1422 
1423         @Override
1424         public boolean accept(AuditEvent event) {
1425             return false;
1426         }
1427 
1428     }
1429 
1430     private static class DummyFileSetViolationCheck extends AbstractFileSetCheck
1431         implements ExternalResourceHolder {
1432 
1433         @Override
1434         protected void processFiltered(File file, FileText fileText) {
1435             log(1, "test");
1436         }
1437 
1438         @Override
1439         public Set<String> getExternalResourceLocations() {
1440             final Set<String> externalResourceLocation = new HashSet<>(1);
1441             externalResourceLocation.add("non_existent_external_resource.xml");
1442             return externalResourceLocation;
1443         }
1444 
1445     }
1446 
1447     private static class DummyFilterSet extends FilterSet implements ExternalResourceHolder {
1448 
1449         @Override
1450         public Set<String> getExternalResourceLocations() {
1451             final Set<String> strings = new HashSet<>();
1452             strings.add("test");
1453             return strings;
1454         }
1455 
1456     }
1457 
1458     private static class DynamicalResourceHolderCheck extends AbstractFileSetCheck
1459         implements ExternalResourceHolder {
1460 
1461         private String firstExternalResourceLocation;
1462         private String secondExternalResourceLocation;
1463 
1464         public void setFirstExternalResourceLocation(String firstExternalResourceLocation) {
1465             this.firstExternalResourceLocation = firstExternalResourceLocation;
1466         }
1467 
1468         public void setSecondExternalResourceLocation(String secondExternalResourceLocation) {
1469             this.secondExternalResourceLocation = secondExternalResourceLocation;
1470         }
1471 
1472         @Override
1473         protected void processFiltered(File file, FileText fileText) {
1474             // there is no need in implementation of the method
1475         }
1476 
1477         @Override
1478         public Set<String> getExternalResourceLocations() {
1479             final Set<String> locations = new HashSet<>();
1480             locations.add(firstExternalResourceLocation);
1481             // Attempt to change the behaviour of the check dynamically
1482             if (secondExternalResourceLocation != null) {
1483                 locations.add(secondExternalResourceLocation);
1484             }
1485             return locations;
1486         }
1487 
1488     }
1489 
1490     private static class CheckWhichDoesNotRequireCommentNodes extends AbstractCheck {
1491 
1492         /** Number of children of method definition token. */
1493         private static final int METHOD_DEF_CHILD_COUNT = 7;
1494 
1495         @Override
1496         public int[] getDefaultTokens() {
1497             return new int[] {TokenTypes.METHOD_DEF};
1498         }
1499 
1500         @Override
1501         public int[] getAcceptableTokens() {
1502             return new int[] {TokenTypes.METHOD_DEF};
1503         }
1504 
1505         @Override
1506         public int[] getRequiredTokens() {
1507             return new int[] {TokenTypes.METHOD_DEF};
1508         }
1509 
1510         @Override
1511         public void visitToken(DetailAST ast) {
1512             if (ast.findFirstToken(TokenTypes.MODIFIERS).findFirstToken(
1513                     TokenTypes.BLOCK_COMMENT_BEGIN) != null) {
1514                 log(ast, "AST has incorrect structure structure."
1515                     + " The check does not require comment nodes but there were comment nodes"
1516                     + " in the AST.");
1517             }
1518             final int childCount = ast.getChildCount();
1519             if (childCount != METHOD_DEF_CHILD_COUNT) {
1520                 final String msg = String.format(Locale.getDefault(),
1521                     "AST node in no comment tree has wrong number of children. "
1522                             + "Expected is %d but was %d",
1523                     METHOD_DEF_CHILD_COUNT, childCount);
1524                 log(ast, msg);
1525             }
1526             // count children where comment lives
1527             int actualChildCount = 0;
1528             for (DetailAST child = ast.getFirstChild().getFirstChild(); child != null; child =
1529                     child.getNextSibling()) {
1530                 actualChildCount++;
1531             }
1532             final int cacheChildCount = ast.getFirstChild().getChildCount();
1533             if (cacheChildCount != actualChildCount) {
1534                 final String msg = String.format(Locale.getDefault(),
1535                         "AST node with no comment has wrong number of children. "
1536                                 + "Expected is %d but was %d",
1537                         cacheChildCount, actualChildCount);
1538                 log(ast, msg);
1539             }
1540         }
1541 
1542     }
1543 
1544     private static class CheckWhichRequiresCommentNodes extends AbstractCheck {
1545 
1546         /** Number of children of method definition token. */
1547         private static final int METHOD_DEF_CHILD_COUNT = 7;
1548 
1549         @Override
1550         public boolean isCommentNodesRequired() {
1551             return true;
1552         }
1553 
1554         @Override
1555         public int[] getDefaultTokens() {
1556             return new int[] {TokenTypes.METHOD_DEF};
1557         }
1558 
1559         @Override
1560         public int[] getAcceptableTokens() {
1561             return new int[] {TokenTypes.METHOD_DEF};
1562         }
1563 
1564         @Override
1565         public int[] getRequiredTokens() {
1566             return new int[] {TokenTypes.METHOD_DEF};
1567         }
1568 
1569         @Override
1570         public void visitToken(DetailAST ast) {
1571             if (ast.findFirstToken(TokenTypes.MODIFIERS).findFirstToken(
1572                     TokenTypes.BLOCK_COMMENT_BEGIN) == null) {
1573                 log(ast, "Incorrect AST structure.");
1574             }
1575             final int childCount = ast.getChildCount();
1576             if (childCount != METHOD_DEF_CHILD_COUNT) {
1577                 final String msg = String.format(Locale.getDefault(),
1578                     "AST node in comment tree has wrong number of children. "
1579                             + "Expected is %d but was %d",
1580                     METHOD_DEF_CHILD_COUNT, childCount);
1581                 log(ast, msg);
1582             }
1583             // count children where comment lives
1584             int actualChildCount = 0;
1585             for (DetailAST child = ast.getFirstChild().getFirstChild(); child != null; child =
1586                     child.getNextSibling()) {
1587                 actualChildCount++;
1588             }
1589             final int cacheChildCount = ast.getFirstChild().getChildCount();
1590             if (cacheChildCount != actualChildCount) {
1591                 final String msg = String.format(Locale.getDefault(),
1592                         "AST node with comment has wrong number of children. "
1593                                 + "Expected is %d but was %d",
1594                         cacheChildCount, actualChildCount);
1595                 log(ast, msg);
1596             }
1597         }
1598 
1599     }
1600 
1601     private static class CheckWhichThrowsError extends AbstractCheck {
1602 
1603         @Override
1604         public int[] getDefaultTokens() {
1605             return new int[] {TokenTypes.CLASS_DEF};
1606         }
1607 
1608         @Override
1609         public int[] getAcceptableTokens() {
1610             return new int[] {TokenTypes.CLASS_DEF};
1611         }
1612 
1613         @Override
1614         public int[] getRequiredTokens() {
1615             return new int[] {TokenTypes.CLASS_DEF};
1616         }
1617 
1618         @Override
1619         public void visitToken(DetailAST ast) {
1620             throw new IndexOutOfBoundsException("test");
1621         }
1622 
1623     }
1624 
1625     private static class DummyFileSet extends AbstractFileSetCheck {
1626 
1627         private final List<String> methodCalls = new ArrayList<>();
1628 
1629         private boolean initCalled;
1630 
1631         @Override
1632         public void init() {
1633             super.init();
1634             initCalled = true;
1635         }
1636 
1637         @Override
1638         public void beginProcessing(String charset) {
1639             methodCalls.add("beginProcessing");
1640             super.beginProcessing(charset);
1641         }
1642 
1643         @Override
1644         public void finishProcessing() {
1645             methodCalls.add("finishProcessing");
1646             super.finishProcessing();
1647         }
1648 
1649         @Override
1650         protected void processFiltered(File file, FileText fileText) {
1651             methodCalls.add("processFiltered");
1652         }
1653 
1654         @Override
1655         public void destroy() {
1656             methodCalls.add("destroy");
1657             super.destroy();
1658         }
1659 
1660         public List<String> getMethodCalls() {
1661             return Collections.unmodifiableList(methodCalls);
1662         }
1663 
1664         public boolean isInitCalled() {
1665             return initCalled;
1666         }
1667 
1668         public MessageDispatcher getInternalMessageDispatcher() {
1669             return getMessageDispatcher();
1670         }
1671 
1672     }
1673 
1674     private static class VerifyPositionAfterTabFileSet extends AbstractFileSetCheck {
1675 
1676         @Override
1677         protected void processFiltered(File file, FileText fileText) {
1678             int lineNumber = 0;
1679             for (String line : getFileContents().getLines()) {
1680                 final int position = line.lastIndexOf('\t');
1681                 lineNumber++;
1682 
1683                 if (position != -1) {
1684                     log(lineNumber, position + 1, "violation");
1685                 }
1686             }
1687         }
1688 
1689     }
1690 
1691 }