View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2024 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.checks.naming.AbstractNameCheck.MSG_INVALID_PATTERN;
24  import static org.mockito.ArgumentMatchers.any;
25  import static org.mockito.Mockito.CALLS_REAL_METHODS;
26  import static org.mockito.Mockito.doThrow;
27  import static org.mockito.Mockito.mock;
28  
29  import java.io.File;
30  import java.io.Writer;
31  import java.nio.charset.StandardCharsets;
32  import java.nio.file.Files;
33  import java.nio.file.StandardCopyOption;
34  import java.util.ArrayList;
35  import java.util.Arrays;
36  import java.util.Collection;
37  import java.util.Collections;
38  import java.util.HashMap;
39  import java.util.HashSet;
40  import java.util.List;
41  import java.util.Map;
42  import java.util.Set;
43  import java.util.UUID;
44  import java.util.regex.Matcher;
45  import java.util.regex.Pattern;
46  
47  import org.junit.jupiter.api.Test;
48  import org.junit.jupiter.api.io.TempDir;
49  import org.mockito.MockedConstruction;
50  import org.mockito.MockedStatic;
51  import org.mockito.Mockito;
52  import org.mockito.internal.util.Checks;
53  
54  import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
55  import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
56  import com.puppycrawl.tools.checkstyle.api.Configuration;
57  import com.puppycrawl.tools.checkstyle.api.Context;
58  import com.puppycrawl.tools.checkstyle.api.DetailAST;
59  import com.puppycrawl.tools.checkstyle.api.FileContents;
60  import com.puppycrawl.tools.checkstyle.api.FileText;
61  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
62  import com.puppycrawl.tools.checkstyle.checks.NoCodeInFileCheck;
63  import com.puppycrawl.tools.checkstyle.checks.coding.EmptyStatementCheck;
64  import com.puppycrawl.tools.checkstyle.checks.coding.HiddenFieldCheck;
65  import com.puppycrawl.tools.checkstyle.checks.design.OneTopLevelClassCheck;
66  import com.puppycrawl.tools.checkstyle.checks.indentation.CommentsIndentationCheck;
67  import com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocPackageCheck;
68  import com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocParagraphCheck;
69  import com.puppycrawl.tools.checkstyle.checks.naming.ConstantNameCheck;
70  import com.puppycrawl.tools.checkstyle.checks.naming.MemberNameCheck;
71  import com.puppycrawl.tools.checkstyle.checks.naming.ParameterNameCheck;
72  import com.puppycrawl.tools.checkstyle.checks.naming.TypeNameCheck;
73  import com.puppycrawl.tools.checkstyle.checks.whitespace.WhitespaceAfterCheck;
74  import com.puppycrawl.tools.checkstyle.checks.whitespace.WhitespaceAroundCheck;
75  import com.puppycrawl.tools.checkstyle.filters.SuppressWithNearbyCommentFilter;
76  import com.puppycrawl.tools.checkstyle.filters.SuppressionXpathFilter;
77  import com.puppycrawl.tools.checkstyle.internal.utils.TestUtil;
78  import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
79  
80  /**
81   * TreeWalkerTest.
82   *
83   * @noinspection ClassWithTooManyDependencies because we are less strict with tests.
84   * @noinspectionreason ClassWithTooManyDependencies - complex tests require a
85   *      large number of imports
86   */
87  public class TreeWalkerTest extends AbstractModuleTestSupport {
88  
89      @TempDir
90      public File temporaryFolder;
91  
92      @Override
93      protected String getPackageLocation() {
94          return "com/puppycrawl/tools/checkstyle/treewalker";
95      }
96  
97      @Test
98      public void testProperFileExtension() throws Exception {
99          final String path = getPath("InputTreeWalkerProperFileExtension.java");
100         final String[] expected = {
101             "10:27: " + getCheckMessage(ConstantNameCheck.class,
102                         MSG_INVALID_PATTERN, "k", "^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$"),
103         };
104         verifyWithInlineConfigParserTwice(path, expected);
105     }
106 
107     /**
108      * This test is needed for 100% coverage.
109      * The Pitest reports some conditions as redundant, for example:
110      * <pre>
111      *     if (!collection.isEmpty()) { // This may be omitted.
112      *         Object value = doSomeHardJob();
113      *         for (Item item : collection) {
114      *             item.accept(value);
115      *         }
116      *     }
117      * </pre>
118      * But we really want to avoid calls to {@code doSomeHardJob} method.
119      * To make this condition mandatory, we need to broke one branch.
120      * In this case, mocking {@code TreeWalkerAuditEvent} will cause
121      * {@code getFilteredViolations} to fail. This prevents the condition
122      * <pre>
123      *     if (filters.isEmpty())
124      * </pre>
125      * in {@link TreeWalker#processFiltered(File, FileText)} to survive with Pitest mutations.
126      *
127      * @throws Exception if an error occurs
128      */
129     @Test
130     public void testNoAuditEventsWithoutFilters() throws Exception {
131         final String[] expected = {
132             "10:1: " + getCheckMessage(OneTopLevelClassCheck.class,
133                     OneTopLevelClassCheck.MSG_KEY, "InputTreeWalkerInner"),
134         };
135         try (MockedConstruction<TreeWalkerAuditEvent> mocked =
136                  Mockito.mockConstruction(TreeWalkerAuditEvent.class, (mock, context) -> {
137                      throw new CheckstyleException("No audit events expected");
138                  })) {
139             verifyWithInlineConfigParserTwice(getPath("InputTreeWalker.java"), expected);
140         }
141     }
142 
143     /**
144      * This test is needed for 100% coverage. The method {@link Mockito#mockStatic} is used to
145      * ensure that the {@code if (!ordinaryChecks.isEmpty())} condition cannot be removed.
146      */
147     @Test
148     public void testConditionRequiredWithoutOrdinaryChecks() throws Exception {
149         final String[] expected = {
150             "7: " + getCheckMessage(JavadocParagraphCheck.class,
151                     JavadocParagraphCheck.MSG_REDUNDANT_PARAGRAPH),
152         };
153         final String path = getPath("InputTreeWalkerJavadoc.java");
154         final DetailAST mockAst = mock();
155         final DetailAST realAst = JavaParser.parseFile(new File(path),
156                 JavaParser.Options.WITH_COMMENTS);
157         // Ensure that there is no calls to walk(..., AstState.ORDINARY)
158         doThrow(IllegalStateException.class).when(mockAst).getFirstChild();
159         try (MockedStatic<JavaParser> parser = Mockito.mockStatic(JavaParser.class)) {
160             parser.when(() -> JavaParser.parse(any(FileContents.class))).thenReturn(mockAst);
161             // This will re-enable walk(..., AstState.WITH_COMMENTS)
162             parser.when(() -> JavaParser.appendHiddenCommentNodes(mockAst)).thenReturn(realAst);
163 
164             verifyWithInlineConfigParserTwice(path, expected);
165         }
166     }
167 
168     /**
169      * This test is needed for 100% coverage. The method {@link Mockito#mockStatic} is used to
170      * ensure that the {@code if (!commentChecks.isEmpty())} condition cannot be removed.
171      */
172     @Test
173     public void testConditionRequiredWithoutCommentChecks() throws Exception {
174         final String[] expected = {
175             "10:1: " + getCheckMessage(OneTopLevelClassCheck.class,
176                     OneTopLevelClassCheck.MSG_KEY, "InputTreeWalkerInner"),
177         };
178         try (MockedStatic<JavaParser> parser =
179                      Mockito.mockStatic(JavaParser.class, CALLS_REAL_METHODS)) {
180             // Ensure that there is no calls to walk(..., AstState.WITH_COMMENTS)
181             parser.when(() -> JavaParser.appendHiddenCommentNodes(any(DetailAST.class)))
182                     .thenThrow(IllegalStateException.class);
183 
184             verifyWithInlineConfigParserTwice(getPath("InputTreeWalker.java"), expected);
185         }
186     }
187 
188     @Test
189     public void testImproperFileExtension() throws Exception {
190         final String regularFilePath = getPath("InputTreeWalkerImproperFileExtension.java");
191         final File originalFile = new File(regularFilePath);
192         final File tempFile = new File(temporaryFolder, "file.pdf");
193         Files.copy(originalFile.toPath(), tempFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
194         final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
195         verifyWithInlineConfigParserTwice(tempFile.getPath(), expected);
196     }
197 
198     @Test
199     public void testAcceptableTokens()
200             throws Exception {
201         final DefaultConfiguration checkConfig =
202             createModuleConfig(HiddenFieldCheck.class);
203         checkConfig.addProperty("tokens", "VARIABLE_DEF, ENUM_DEF, CLASS_DEF, METHOD_DEF,"
204                 + "IMPORT");
205         try {
206             execute(checkConfig, getPath("InputTreeWalker.java"));
207             assertWithMessage("CheckstyleException is expected").fail();
208         }
209         catch (CheckstyleException ex) {
210             final String errorMsg = ex.getMessage();
211             final Pattern expected = Pattern.compile(Pattern.quote("cannot initialize module"
212                     + " com.puppycrawl.tools.checkstyle.TreeWalker - Token ")
213                     + "\"(ENUM_DEF|CLASS_DEF|METHOD_DEF|IMPORT)\""
214                     + Pattern.quote(" was not found in Acceptable tokens list in check"
215                     + " com.puppycrawl.tools.checkstyle.checks.coding.HiddenFieldCheck"));
216 
217             final Matcher errorMsgMatcher = expected.matcher(errorMsg);
218             assertWithMessage("Failure for: " + errorMsg)
219                     .that(errorMsgMatcher.matches())
220                     .isTrue();
221         }
222     }
223 
224     @Test
225     public void testOnEmptyFile() throws Exception {
226         final DefaultConfiguration checkConfig = createModuleConfig(HiddenFieldCheck.class);
227         final String uniqueFileName = "file_" + UUID.randomUUID() + ".java";
228         final File emptyFile = new File(temporaryFolder, uniqueFileName);
229         emptyFile.createNewFile();
230         execute(checkConfig, emptyFile.getPath());
231         final long fileSize = Files.size(emptyFile.toPath());
232         assertWithMessage("File should be empty")
233                 .that(fileSize)
234                 .isEqualTo(0);
235     }
236 
237     @Test
238     public void testWithCheckNotHavingTreeWalkerAsParent() throws Exception {
239         final DefaultConfiguration checkConfig = createModuleConfig(JavadocPackageCheck.class);
240 
241         try {
242             final String uniqueFileName = "junit_" + UUID.randomUUID() + ".java";
243             final File filePath = new File(temporaryFolder, uniqueFileName);
244             execute(createTreeWalkerConfig(checkConfig), filePath.toString());
245             assertWithMessage("CheckstyleException is expected").fail();
246         }
247         catch (CheckstyleException exception) {
248             assertWithMessage("Error message is unexpected")
249                     .that(exception.getMessage())
250                     .contains("TreeWalker is not allowed as a parent of");
251         }
252     }
253 
254     @Test
255     public void testSetupChildExceptions() {
256         final TreeWalker treeWalker = new TreeWalker();
257         final PackageObjectFactory factory = new PackageObjectFactory(
258                 new HashSet<>(), Thread.currentThread().getContextClassLoader());
259         treeWalker.setModuleFactory(factory);
260 
261         final Configuration config = new DefaultConfiguration("java.lang.String");
262         try {
263             treeWalker.setupChild(config);
264             assertWithMessage("Exception is expected").fail();
265         }
266         catch (CheckstyleException ex) {
267             assertWithMessage("Error message is not expected")
268                 .that(ex.getMessage())
269                 .isEqualTo("TreeWalker is not allowed as a parent of java.lang.String "
270                     + "Please review 'Parent Module' section for this Check in "
271                     + "web documentation if Check is standard.");
272         }
273     }
274 
275     @Test
276     public void testSettersForParameters() throws Exception {
277         final TreeWalker treeWalker = new TreeWalker();
278         final DefaultConfiguration config = new DefaultConfiguration("default config");
279         treeWalker.setTabWidth(1);
280         treeWalker.configure(config);
281 
282         final int tabWidth = TestUtil.getInternalState(treeWalker, "tabWidth");
283         assertWithMessage("Invalid setter result")
284             .that(tabWidth)
285             .isEqualTo(1);
286         final Object configuration = TestUtil.getInternalState(treeWalker, "configuration");
287         assertWithMessage("Invalid configuration")
288             .that(configuration)
289             .isEqualTo(config);
290     }
291 
292     @Test
293     public void testForInvalidCheckImplementation() throws Exception {
294         final DefaultConfiguration checkConfig = createModuleConfig(BadJavaDocCheck.class);
295         final String uniqueFileName = "file_" + UUID.randomUUID() + ".java";
296         final File pathToEmptyFile = new File(temporaryFolder, uniqueFileName);
297 
298         try {
299             execute(checkConfig, pathToEmptyFile.toString());
300             assertWithMessage("Exception is expected").fail();
301         }
302         catch (CheckstyleException ex) {
303             assertWithMessage("Error message is unexpected")
304                     .that(ex.getMessage())
305                     .isEqualTo("cannot initialize module com.puppycrawl.tools.checkstyle."
306                             + "TreeWalker - Check 'com.puppycrawl.tools.checkstyle."
307                             + "TreeWalkerTest$BadJavaDocCheck' waits for comment type token "
308                             + "('SINGLE_LINE_COMMENT') and should override "
309                             + "'isCommentNodesRequired()' method to return 'true'");
310             assertWithMessage("Error message is unexpected")
311                     .that(ex.getMessage())
312                     .contains("isCommentNodesRequired");
313         }
314     }
315 
316     @Test
317     public void testProcessNonJavaFiles() throws Exception {
318         final TreeWalker treeWalker = new TreeWalker();
319         final PackageObjectFactory factory = new PackageObjectFactory(
320             new HashSet<>(), Thread.currentThread().getContextClassLoader());
321         treeWalker.setModuleFactory(factory);
322         treeWalker.configure(new DefaultConfiguration("default config"));
323         final DefaultConfiguration childConfig = createModuleConfig(JavadocParagraphCheck.class);
324         treeWalker.setupChild(childConfig);
325         final File file = new File("input.java");
326         final List<String> lines =
327             new ArrayList<>(Arrays.asList("package com.puppycrawl.tools.checkstyle;", "",
328                 "error public class InputTreeWalkerFileWithViolation {}"));
329         final FileText fileText = new FileText(file, lines);
330         treeWalker.setFileContents(new FileContents(fileText));
331         try {
332             treeWalker.processFiltered(file, fileText);
333             assertWithMessage("Exception expected").fail();
334         }
335         catch (CheckstyleException ex) {
336             assertWithMessage("Invalid exception message")
337                 .that(ex.getMessage())
338                 .isEqualTo("IllegalStateException occurred while parsing file input.java.");
339         }
340     }
341 
342     @Test
343     public void testProcessNonJavaFilesWithoutException() throws Exception {
344         final TreeWalker treeWalker = new TreeWalker();
345         treeWalker.setTabWidth(1);
346         treeWalker.configure(new DefaultConfiguration("default config"));
347         final File file = new File(getPath("InputTreeWalkerNotJava.xml"));
348         final FileText fileText = new FileText(file, StandardCharsets.ISO_8859_1.name());
349         treeWalker.processFiltered(file, fileText);
350         final Collection<Checks> checks = TestUtil.getInternalState(treeWalker, "ordinaryChecks");
351         assertWithMessage("No checks -> No parsing")
352             .that(checks)
353             .isEmpty();
354     }
355 
356     @Test
357     public void testWithCacheWithNoViolation() throws Exception {
358         final String path = getPath("InputTreeWalkerWithCacheWithNoViolation.java");
359         final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
360         verifyWithInlineConfigParserTwice(path, expected);
361     }
362 
363     @Test
364     public void testProcessWithParserThrowable() throws Exception {
365         final TreeWalker treeWalker = new TreeWalker();
366         treeWalker.configure(createModuleConfig(TypeNameCheck.class));
367         final PackageObjectFactory factory = new PackageObjectFactory(
368             new HashSet<>(), Thread.currentThread().getContextClassLoader());
369         treeWalker.setModuleFactory(factory);
370         treeWalker.setupChild(createModuleConfig(TypeNameCheck.class));
371         final File file = new File(temporaryFolder, "file.java");
372         final List<String> lines = new ArrayList<>();
373         lines.add(" classD a {} ");
374         final FileText fileText = new FileText(file, lines);
375         treeWalker.setFileContents(new FileContents(fileText));
376         try {
377             treeWalker.processFiltered(file, fileText);
378             assertWithMessage("Exception is expected").fail();
379         }
380         catch (CheckstyleException exception) {
381             assertWithMessage("Error message is unexpected")
382                     .that(exception.getMessage())
383                     .contains("occurred while parsing file");
384         }
385     }
386 
387     @Test
388     public void testProcessWithRecognitionException() throws Exception {
389         final TreeWalker treeWalker = new TreeWalker();
390         treeWalker.configure(createModuleConfig(TypeNameCheck.class));
391         final PackageObjectFactory factory = new PackageObjectFactory(
392             new HashSet<>(), Thread.currentThread().getContextClassLoader());
393         treeWalker.setModuleFactory(factory);
394         treeWalker.setupChild(createModuleConfig(TypeNameCheck.class));
395         final File file = new File(temporaryFolder, "file.java");
396         final List<String> lines = new ArrayList<>();
397         lines.add(" class a%$# {} ");
398         final FileText fileText = new FileText(file, lines);
399         treeWalker.setFileContents(new FileContents(fileText));
400         try {
401             treeWalker.processFiltered(file, fileText);
402             assertWithMessage("Exception is expected").fail();
403         }
404         catch (CheckstyleException exception) {
405             assertWithMessage("Error message is unexpected")
406                     .that(exception.getMessage())
407                     .contains("IllegalStateException occurred while parsing file");
408         }
409     }
410 
411     @Test
412     public void testRequiredTokenIsEmptyIntArray() throws Exception {
413         final File file = new File(temporaryFolder, "file.java");
414         try (Writer writer = Files.newBufferedWriter(file.toPath(), StandardCharsets.UTF_8)) {
415             final String configComment = "/*\n"
416                     + "com.puppycrawl.tools.checkstyle.TreeWalkerTest"
417                     + "$RequiredTokenIsEmptyIntArray\n\n"
418                     + "*/";
419             writer.write(configComment);
420         }
421         final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
422         verifyWithInlineConfigParserTwice(file.getPath(), expected);
423     }
424 
425     @Test
426     public void testBehaviourWithZeroChecks() throws Exception {
427         final TreeWalker treeWalker = new TreeWalker();
428         final PackageObjectFactory factory = new PackageObjectFactory(
429                 new HashSet<>(), Thread.currentThread().getContextClassLoader());
430         treeWalker.setModuleFactory(factory);
431         // create file that should throw exception
432         final File file = new File(temporaryFolder, "file.java");
433         final FileText fileText = new FileText(file, new ArrayList<>());
434 
435         treeWalker.processFiltered(file, fileText);
436         final Collection<Checks> checks = TestUtil.getInternalState(treeWalker, "ordinaryChecks");
437         assertWithMessage("No checks -> No parsing")
438             .that(checks)
439             .isEmpty();
440     }
441 
442     @Test
443     public void testBehaviourWithOrdinaryAndCommentChecks() throws Exception {
444         final TreeWalker treeWalker = new TreeWalker();
445         treeWalker.configure(createModuleConfig(TypeNameCheck.class));
446         treeWalker.configure(createModuleConfig(CommentsIndentationCheck.class));
447         final PackageObjectFactory factory = new PackageObjectFactory(
448                 new HashSet<>(), Thread.currentThread().getContextClassLoader());
449         treeWalker.setModuleFactory(factory);
450         treeWalker.setupChild(createModuleConfig(TypeNameCheck.class));
451         treeWalker.setupChild(createModuleConfig(CommentsIndentationCheck.class));
452         final File file = new File(temporaryFolder, "file.java");
453         final List<String> lines = new ArrayList<>();
454         lines.add(" class a%$# {} ");
455         final FileText fileText = new FileText(file, lines);
456         treeWalker.setFileContents(new FileContents(fileText));
457 
458         try {
459             treeWalker.processFiltered(file, fileText);
460             assertWithMessage("file is not compilable, exception is expected").fail();
461         }
462         catch (CheckstyleException exception) {
463             final String message = "IllegalStateException occurred while parsing file";
464             assertWithMessage("Error message is unexpected")
465                     .that(exception.getMessage())
466                     .contains(message);
467         }
468     }
469 
470     @Test
471     public void testSetupChild() throws Exception {
472         final TreeWalker treeWalker = new TreeWalker();
473         final PackageObjectFactory factory = new PackageObjectFactory(
474                 new HashSet<>(), Thread.currentThread().getContextClassLoader());
475         treeWalker.setModuleFactory(factory);
476         treeWalker.setTabWidth(99);
477         treeWalker.finishLocalSetup();
478 
479         final Configuration config = new DefaultConfiguration(
480                 XpathFileGeneratorAstFilter.class.getName());
481 
482         treeWalker.setupChild(config);
483 
484         final Set<TreeWalkerFilter> filters = TestUtil.getInternalState(treeWalker, "filters");
485         final int tabWidth = TestUtil.getInternalState(filters.iterator().next(), "tabWidth");
486 
487         assertWithMessage("expected tab width")
488             .that(tabWidth)
489             .isEqualTo(99);
490     }
491 
492     @Test
493     public void testBehaviourWithChecksAndFilters() throws Exception {
494 
495         final String[] expected = {
496             "17:17: " + getCheckMessage(MemberNameCheck.class, "name.invalidPattern", "P",
497                     "^[a-z][a-zA-Z0-9]*$"),
498             "12:17: " + getCheckMessage(MemberNameCheck.class, "name.invalidPattern", "I",
499                     "^[a-z][a-zA-Z0-9]*$"),
500         };
501 
502         verifyWithInlineConfigParserTwice(
503                 getPath("InputTreeWalkerSuppressionCommentFilter.java"),
504                 expected);
505     }
506 
507     @Test
508     public void testMultiCheckOrder() throws Exception {
509 
510         final String[] expected = {
511             "13:9: " + getCheckMessage(WhitespaceAfterCheck.class, "ws.notFollowed", "if"),
512             "13:9: " + getCheckMessage(WhitespaceAroundCheck.class, "ws.notFollowed", "if"),
513         };
514 
515         verifyWithInlineConfigParserTwice(
516                 getPath("InputTreeWalkerMultiCheckOrder.java"),
517                 expected);
518     }
519 
520     @Test
521     public void testMultiCheckOfSameTypeNoIdResultsInOrderingByHash() throws Exception {
522 
523         final String[] expected = {
524             "15:28: " + getCheckMessage(ParameterNameCheck.class,
525                     "name.invalidPattern", "V2", "^[a-z]([a-z0-9][a-zA-Z0-9]*)?$"),
526             "17:25: " + getCheckMessage(ParameterNameCheck.class,
527                     "name.invalidPattern", "b", "^[a-z][a-z0-9][a-zA-Z0-9]*$"),
528         };
529 
530         verifyWithInlineConfigParserTwice(
531                 getPath("InputTreeWalkerMultiCheckOrder2.java"),
532                 expected);
533     }
534 
535     @Test
536     public void testFinishLocalSetupFullyInitialized() {
537         final TreeWalker treeWalker = new TreeWalker();
538         treeWalker.setSeverity("error");
539         treeWalker.setTabWidth(100);
540         treeWalker.finishLocalSetup();
541 
542         final Context context = TestUtil.getInternalState(treeWalker, "childContext");
543         assertWithMessage("Severity differs from expected")
544             .that(context.get("severity"))
545             .isEqualTo("error");
546         assertWithMessage("Tab width differs from expected")
547             .that(context.get("tabWidth"))
548             .isEqualTo(String.valueOf(100));
549     }
550 
551     @Test
552     public void testCheckInitIsCalledInTreeWalker() throws Exception {
553         final DefaultConfiguration checkConfig =
554                 createModuleConfig(VerifyInitCheck.class);
555         final String uniqueFileName = "file_" + UUID.randomUUID() + ".pdf";
556         final File file = new File(temporaryFolder, uniqueFileName);
557         execute(checkConfig, file.getPath());
558         assertWithMessage("Init was not called")
559                 .that(VerifyInitCheck.isInitWasCalled())
560                 .isTrue();
561     }
562 
563     @Test
564     public void testCheckDestroyIsCalledInTreeWalker() throws Exception {
565         VerifyDestroyCheck.resetDestroyWasCalled();
566         final DefaultConfiguration checkConfig =
567                 createModuleConfig(VerifyDestroyCheck.class);
568         final String uniqueFileName = "file_" + UUID.randomUUID() + ".pdf";
569         final File file = new File(temporaryFolder, uniqueFileName);
570         execute(checkConfig, file.getPath());
571         assertWithMessage("Destroy was not called")
572                 .that(VerifyDestroyCheck.isDestroyWasCalled())
573                 .isTrue();
574     }
575 
576     @Test
577     public void testCommentCheckDestroyIsCalledInTreeWalker() throws Exception {
578         VerifyDestroyCheck.resetDestroyWasCalled();
579         final DefaultConfiguration checkConfig =
580                 createModuleConfig(VerifyDestroyCommentCheck.class);
581         final String uniqueFileName = "file_" + UUID.randomUUID() + ".pdf";
582         final File file = new File(temporaryFolder, uniqueFileName);
583         execute(checkConfig, file.getPath());
584         assertWithMessage("Destroy was not called")
585                 .that(VerifyDestroyCheck.isDestroyWasCalled())
586                 .isTrue();
587     }
588 
589     @Test
590     public void testCacheWhenFileExternalResourceContentDoesNotChange() throws Exception {
591         final DefaultConfiguration filterConfig = createModuleConfig(SuppressionXpathFilter.class);
592         filterConfig.addProperty("file", getPath("InputTreeWalkerSuppressionXpathFilter.xml"));
593         final DefaultConfiguration treeWalkerConfig = createModuleConfig(TreeWalker.class);
594         treeWalkerConfig.addChild(filterConfig);
595 
596         final DefaultConfiguration checkerConfig = createRootConfig(treeWalkerConfig);
597         final String uniqueFileName1 = "junit_" + UUID.randomUUID() + ".java";
598         final File cacheFile = new File(temporaryFolder, uniqueFileName1);
599         checkerConfig.addProperty("cacheFile", cacheFile.getPath());
600 
601         final String uniqueFileName2 = "file_" + UUID.randomUUID() + ".java";
602         final File filePath = new File(temporaryFolder, uniqueFileName2);
603 
604         execute(checkerConfig, filePath.toString());
605         // One more time to use cache.
606         execute(checkerConfig, filePath.toString());
607 
608         assertWithMessage("External resource is not present in cache")
609                 .that(Files.readString(cacheFile.toPath()))
610                 .contains("InputTreeWalkerSuppressionXpathFilter.xml");
611     }
612 
613     @Test
614     public void testTreeWalkerFilterAbsolutePath() throws Exception {
615         // test is only valid when relative paths are given
616         final String filePath = "src/test/resources/" + getPackageLocation()
617                 + "/InputTreeWalkerSuppressionXpathFilterAbsolute.java";
618 
619         final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
620         verifyWithInlineConfigParserTwice(filePath, expected);
621     }
622 
623     @Test
624     public void testExternalResourceFiltersWithNoExternalResource() throws Exception {
625         final DefaultConfiguration checkConfig = createModuleConfig(EmptyStatementCheck.class);
626         final DefaultConfiguration filterConfig =
627                 createModuleConfig(SuppressWithNearbyCommentFilter.class);
628         final DefaultConfiguration treeWalkerConfig = createModuleConfig(TreeWalker.class);
629         treeWalkerConfig.addChild(checkConfig);
630         treeWalkerConfig.addChild(filterConfig);
631 
632         final DefaultConfiguration checkerConfig = createRootConfig(treeWalkerConfig);
633         final String uniqueFileName1 = "junit_" + UUID.randomUUID() + ".java";
634         final File cacheFile = new File(temporaryFolder, uniqueFileName1);
635         checkerConfig.addProperty("cacheFile", cacheFile.getPath());
636         final String uniqueFileName2 = "junit_" + UUID.randomUUID() + ".java";
637         final File filePath = new File(temporaryFolder, uniqueFileName2);
638 
639         execute(checkerConfig, filePath.toString());
640 
641         final long cacheSize = Files.size(cacheFile.toPath());
642         assertWithMessage("cacheFile should not be empty")
643                 .that(cacheSize)
644                 .isNotEqualTo(0);
645     }
646 
647     /**
648      * This test is checking that Checks execution ordered by name.
649      *
650      * @throws Exception if file is not found
651      */
652     @Test
653     public void testOrderOfCheckExecution() throws Exception {
654 
655         final DefaultConfiguration configuration1 = createModuleConfig(AaCheck.class);
656         configuration1.addProperty("id", "2");
657         final DefaultConfiguration configuration2 = createModuleConfig(BbCheck.class);
658         configuration2.addProperty("id", "1");
659 
660         final DefaultConfiguration treeWalkerConfig = createModuleConfig(TreeWalker.class);
661         treeWalkerConfig.addChild(configuration2);
662         treeWalkerConfig.addChild(configuration1);
663 
664         final List<File> files =
665                 Collections.singletonList(new File(getPath("InputTreeWalker2.java")));
666         final Checker checker = createChecker(treeWalkerConfig);
667 
668         try {
669             checker.process(files);
670             assertWithMessage("exception is expected").fail();
671         }
672         catch (CheckstyleException exception) {
673             assertWithMessage("wrong order of Check executions")
674                     .that(exception.getCause().getMessage())
675                     .isEqualTo(AaCheck.class.toString());
676         }
677     }
678 
679     @Test
680     public void testSkipFileOnJavaParseExceptionTrue() throws Exception {
681         final DefaultConfiguration config = createModuleConfig(TreeWalker.class);
682         config.addProperty("skipFileOnJavaParseException", "true");
683         config.addProperty("javaParseExceptionSeverity", "ignore");
684         config.addChild(createModuleConfig(ConstantNameCheck.class));
685 
686         final File[] files = {
687             new File(getNonCompilablePath("InputTreeWalkerSkipParsingException.java")),
688             new File(getPath("InputTreeWalkerProperFileExtension.java")),
689             new File(getNonCompilablePath("InputTreeWalkerSkipParsingException2.java")),
690         };
691 
692         final Checker checker = createChecker(config);
693         final Map<String, List<String>> expectedViolation = new HashMap<>();
694         expectedViolation.put(getPath("InputTreeWalkerProperFileExtension.java"),
695                 Collections.singletonList(
696                         "10:27: " + getCheckMessage(ConstantNameCheck.class,
697                         MSG_INVALID_PATTERN, "k", "^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$")));
698         verify(checker, files, expectedViolation);
699     }
700 
701     /**
702      * This test method is not using the regular {@code verify} methods
703      * because it is testing the behavior of {@code TreeWalker} when it should
704      * not skip files with parse exception. Instead, it should throw {@code exception}.
705      * The test verifies this behavior by attempting to process files
706      * that are known to cause a Java parse exception and asserting that
707      * the expected {@code exception} is indeed thrown.
708      *
709      */
710     @Test
711     public void testSkipFileOnJavaParseExceptionFalse() throws Exception {
712         final DefaultConfiguration config = createModuleConfig(TreeWalker.class);
713         config.addProperty("skipFileOnJavaParseException", "false");
714         config.addChild(createModuleConfig(ConstantNameCheck.class));
715 
716         final String[] files = {
717             getNonCompilablePath("InputTreeWalkerSkipParsingException2.java"),
718             getPath("InputTreeWalkerProperFileExtension.java"),
719             getNonCompilablePath("InputTreeWalkerSkipParsingException.java"),
720         };
721         final Exception ex = TestUtil.getExpectedThrowable(CheckstyleException.class,
722                 () -> execute(config, files),
723                 "Exception is expected");
724         assertWithMessage("Error message is unexpected")
725                 .that(ex.getMessage())
726                 .contains("Exception was thrown while processing");
727     }
728 
729     @Test
730     public void testSkipFileOnJavaParseExceptionConfigSeverityIgnore() throws Exception {
731         final String path =
732                 getNonCompilablePath(
733                         "InputTreeWalkerSkipParsingExceptionConfigSeverityIgnore.java");
734         final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
735         verifyWithInlineXmlConfig(path, expected);
736     }
737 
738     @Test
739     public void testSkipFileOnJavaParseExceptionConfigSeverityDefault() throws Exception {
740         final String path =
741                 getNonCompilablePath(
742                         "InputTreeWalkerSkipParsingExceptionConfigSeverityDefault.java");
743         final String[] expected = {
744             "1: " + getCheckMessage(TreeWalker.PARSE_EXCEPTION_MSG, "IllegalStateException")
745                   + " occurred while parsing file " + path + ".",
746         };
747         verifyWithInlineXmlConfig(path, expected);
748     }
749 
750     @Test
751     public void testSkipFileOnJavaParseExceptionSkipChecks() throws Exception {
752         final DefaultConfiguration config = createModuleConfig(TreeWalker.class);
753         config.addProperty("skipFileOnJavaParseException", "true");
754         config.addProperty("javaParseExceptionSeverity", "ignore");
755         config.addChild(createModuleConfig(NoCodeInFileCheck.class));
756 
757         final Checker checker = createChecker(config);
758 
759         final File[] files = {
760             new File(getNonCompilablePath("InputTreeWalkerSkipParsingException.java")),
761             new File(getPath("InputTreeWalkerProperFileExtension.java")),
762             new File(getNonCompilablePath("InputTreeWalkerSkipParsingException2.java")),
763         };
764         final Map<String, List<String>> expectedViolation = new HashMap<>();
765         expectedViolation.put(getPath("InputTreeWalkerProperFileExtension.java"),
766                 new ArrayList<>());
767 
768         verify(checker, files, expectedViolation);
769     }
770 
771     @Test
772     public void testJavaParseExceptionSeverityDefaultError() throws Exception {
773         final DefaultConfiguration config = createModuleConfig(TreeWalker.class);
774         config.addProperty("skipFileOnJavaParseException", "true");
775         config.addChild(createModuleConfig(NoCodeInFileCheck.class));
776 
777         final Checker checker = createChecker(config);
778 
779         final File[] files = {
780             new File(getNonCompilablePath("InputTreeWalkerSkipParsingException.java")),
781             new File(getPath("InputTreeWalkerProperFileExtension.java")),
782         };
783 
784         final Map<String, List<String>> expectedViolation = new HashMap<>();
785 
786         expectedViolation.put(getPath("InputTreeWalkerProperFileExtension.java"),
787                 new ArrayList<>());
788         expectedViolation.put(getNonCompilablePath("InputTreeWalkerSkipParsingException.java"),
789                 List.of("1: Java specific (TreeWalker-based) modules are skipped due to an "
790                         + "exception during parsing - "
791                         + "IllegalStateException occurred while parsing file "
792                         + getNonCompilablePath("InputTreeWalkerSkipParsingException.java") + "."));
793 
794         verify(checker, files, expectedViolation);
795     }
796 
797     public static class BadJavaDocCheck extends AbstractCheck {
798 
799         @Override
800         public int[] getDefaultTokens() {
801             return getAcceptableTokens();
802         }
803 
804         @Override
805         public int[] getAcceptableTokens() {
806             return new int[] {TokenTypes.SINGLE_LINE_COMMENT};
807         }
808 
809         @Override
810         public int[] getRequiredTokens() {
811             return getAcceptableTokens();
812         }
813 
814     }
815 
816     public static class VerifyInitCheck extends AbstractCheck {
817 
818         private static boolean initWasCalled;
819 
820         @Override
821         public int[] getDefaultTokens() {
822             return CommonUtil.EMPTY_INT_ARRAY;
823         }
824 
825         @Override
826         public int[] getAcceptableTokens() {
827             return getDefaultTokens();
828         }
829 
830         @Override
831         public int[] getRequiredTokens() {
832             return getDefaultTokens();
833         }
834 
835         @Override
836         public void init() {
837             super.init();
838             initWasCalled = true;
839         }
840 
841         public static boolean isInitWasCalled() {
842             return initWasCalled;
843         }
844 
845     }
846 
847     public static class VerifyDestroyCheck extends AbstractCheck {
848 
849         private static boolean destroyWasCalled;
850 
851         @Override
852         public int[] getDefaultTokens() {
853             return CommonUtil.EMPTY_INT_ARRAY;
854         }
855 
856         @Override
857         public int[] getAcceptableTokens() {
858             return getDefaultTokens();
859         }
860 
861         @Override
862         public int[] getRequiredTokens() {
863             return getDefaultTokens();
864         }
865 
866         @Override
867         public void destroy() {
868             super.destroy();
869             destroyWasCalled = true;
870         }
871 
872         public static void resetDestroyWasCalled() {
873             destroyWasCalled = false;
874         }
875 
876         public static boolean isDestroyWasCalled() {
877             return destroyWasCalled;
878         }
879 
880     }
881 
882     public static class VerifyDestroyCommentCheck extends VerifyDestroyCheck {
883 
884         @Override
885         public boolean isCommentNodesRequired() {
886             return true;
887         }
888 
889     }
890 
891     public static class AaCheck extends AbstractCheck {
892 
893         @Override
894         public int[] getDefaultTokens() {
895             return new int[0];
896         }
897 
898         @Override
899         public int[] getAcceptableTokens() {
900             return new int[0];
901         }
902 
903         @Override
904         public int[] getRequiredTokens() {
905             return new int[0];
906         }
907 
908         @Override
909         public void beginTree(DetailAST rootAST) {
910             throw new IllegalStateException(AaCheck.class.toString());
911         }
912 
913     }
914 
915     public static class BbCheck extends AbstractCheck {
916 
917         @Override
918         public int[] getDefaultTokens() {
919             return new int[0];
920         }
921 
922         @Override
923         public int[] getAcceptableTokens() {
924             return new int[0];
925         }
926 
927         @Override
928         public int[] getRequiredTokens() {
929             return new int[0];
930         }
931 
932         @Override
933         public void beginTree(DetailAST rootAST) {
934             throw new IllegalStateException(BbCheck.class.toString());
935         }
936 
937     }
938 
939     public static class RequiredTokenIsEmptyIntArray extends AbstractCheck {
940 
941         @Override
942         public int[] getRequiredTokens() {
943             return CommonUtil.EMPTY_INT_ARRAY;
944         }
945 
946         @Override
947         public int[] getDefaultTokens() {
948             return new int[] {TokenTypes.ANNOTATION};
949         }
950 
951         @Override
952         public int[] getAcceptableTokens() {
953             return CommonUtil.EMPTY_INT_ARRAY;
954         }
955 
956     }
957 
958 }