View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2026 the original author or authors.
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  ///////////////////////////////////////////////////////////////////////////////////////////////
19  
20  package com.puppycrawl.tools.checkstyle.internal;
21  
22  import static com.google.common.truth.Truth.assertWithMessage;
23  
24  import java.io.IOException;
25  import java.nio.file.DirectoryStream;
26  import java.nio.file.Files;
27  import java.nio.file.Path;
28  import java.util.HashSet;
29  import java.util.List;
30  import java.util.Locale;
31  import java.util.Map;
32  import java.util.Set;
33  import java.util.function.Function;
34  import java.util.regex.Matcher;
35  import java.util.regex.Pattern;
36  import java.util.stream.Collectors;
37  import java.util.stream.Stream;
38  
39  import org.junit.jupiter.api.BeforeEach;
40  import org.junit.jupiter.api.Test;
41  
42  import com.google.common.base.Splitter;
43  import com.puppycrawl.tools.checkstyle.AbstractModuleTestSupport;
44  import com.puppycrawl.tools.checkstyle.Definitions;
45  import com.puppycrawl.tools.checkstyle.internal.utils.CheckUtil;
46  
47  public class XpathRegressionTest extends AbstractModuleTestSupport {
48  
49      // Checks that not compatible with SuppressionXpathFilter
50      public static final Set<String> INCOMPATIBLE_CHECK_NAMES = Set.of(
51              "NoCodeInFile (reason is that AST is not generated for a file not containing code)",
52              "Regexp (reason is at  #7759)",
53              "RegexpSinglelineJava (reason is at  #7759)"
54      );
55  
56      // Checks that report violations on Javadoc AST nodes (not Java AST).
57      // Incompatible with SuppressionXpathFilter until
58      // https://github.com/checkstyle/checkstyle/issues/5770 is fixed;
59      // then drop this field.
60      public static final Set<String> INCOMPATIBLE_JAVADOC_CHECK_NAMES = Set.of(
61                      "AtclauseOrder",
62                      "IllegalBlockTag",
63                      "JavadocBlockTagLocation",
64                      "JavadocLeadingAsteriskAlign",
65                      "JavadocLinkFirstOccurrence",
66                      "JavadocLinkWellKnownApi",
67                      "JavadocMethod",
68                      "JavadocMissingLeadingAsterisk",
69                      "JavadocMissingWhitespaceAfterAsterisk",
70                      "JavadocNoErrorInThrowsTag",
71                      "JavadocParagraph",
72                      "JavadocParamOrder",
73                      "JavadocRegexp",
74                      "JavadocTagContinuationIndentation",
75                      "JavadocThrowsOrder",
76                      "JavadocType",
77                      "MissingDeprecated",
78                      "NonEmptyAtclauseDescription",
79                      "PreferLiteralJavadocInlineTag",
80                      "PreferCodeOrSnippetJavadocInlineTag",
81                      "RequireEmptyLineBeforeBlockTagGroup",
82                      "SingleLineJavadoc",
83                      "SummaryJavadoc",
84                      "WriteTag"
85      );
86  
87      // Modules that will never have xpath support ever because they not report violations
88      private static final Set<String> NO_VIOLATION_MODULES = Set.of(
89              "SuppressWarningsHolder"
90      );
91  
92      private static final Set<String> SIMPLE_CHECK_NAMES = getSimpleCheckNames();
93      private static final Map<String, String> ALLOWED_DIRECTORY_AND_CHECKS =
94          getAllowedDirectoryAndChecks();
95  
96      private static final Set<String> INTERNAL_MODULES = getInternalModules();
97      private static final DirectoryStream.Filter<Path> IS_DIRECTORY =
98          path -> path.toFile().isDirectory();
99  
100     private Path javaDir;
101     private Path inputDir;
102 
103     private static Set<String> getSimpleCheckNames() {
104         try {
105             return CheckUtil.getSimpleNames(CheckUtil.getCheckstyleChecks());
106         }
107         catch (IOException exc) {
108             throw new ExceptionInInitializerError(exc);
109         }
110     }
111 
112     private static Map<String, String> getAllowedDirectoryAndChecks() {
113         return SIMPLE_CHECK_NAMES
114             .stream()
115             .collect(Collectors.toUnmodifiableMap(
116                 id -> id.toLowerCase(Locale.ENGLISH), Function.identity()));
117     }
118 
119     private static Set<String> getInternalModules() {
120         return Definitions.INTERNAL_MODULES.stream()
121             .map(moduleName -> {
122                 final List<String> packageTokens = Splitter.on(".").splitToList(moduleName);
123                 return packageTokens.getLast();
124             })
125             .collect(Collectors.toUnmodifiableSet());
126     }
127 
128     @BeforeEach
129     public void setUp() throws Exception {
130         javaDir = Path.of("src/it/java/" + getPackageLocation());
131         inputDir = Path.of(getPath(""));
132     }
133 
134     @Override
135     public String getPackageLocation() {
136         return "org/checkstyle/suppressionxpathfilter";
137     }
138 
139     @Override
140     protected String getResourceLocation() {
141         return "it";
142     }
143 
144     @Test
145     public void validateIntegrationTestClassNames() throws Exception {
146         final Set<String> compatibleChecks = new HashSet<>();
147         final Pattern pattern = Pattern.compile("^XpathRegression(.+)Test\\.java$");
148         try (Stream<Path> javaPathsStream = Files.walk(Path.of(javaDir.toString()))) {
149             final List<Path> javaPaths = javaPathsStream.filter(Files::isRegularFile).toList();
150 
151             for (Path path : javaPaths) {
152                 assertWithMessage("%s is not a regular file", path)
153                         .that(Files.isRegularFile(path))
154                         .isTrue();
155                 final String filename = path.toFile().getName();
156                 if (filename.startsWith("Abstract")) {
157                     continue;
158                 }
159 
160                 final Matcher matcher = pattern.matcher(filename);
161                 assertWithMessage(
162                             "Invalid test file: %s, expected pattern: %s", filename, pattern)
163                         .that(matcher.matches())
164                         .isTrue();
165 
166                 final String check = matcher.group(1);
167                 assertWithMessage("Unknown check '%s' in test file: %s", check, filename)
168                         .that(SIMPLE_CHECK_NAMES)
169                         .contains(check);
170 
171                 assertWithMessage(
172                             "Check '%s' is now compatible with SuppressionXpathFilter."
173                                 + " Please update the todo list in"
174                                 + " XpathRegressionTest.INCOMPATIBLE_CHECK_NAMES", check)
175                         .that(INCOMPATIBLE_CHECK_NAMES.contains(check))
176                         .isFalse();
177                 compatibleChecks.add(check);
178             }
179         }
180 
181         // Ensure that all lists are up-to-date
182         final Set<String> allChecks = new HashSet<>(SIMPLE_CHECK_NAMES);
183         allChecks.removeAll(INCOMPATIBLE_JAVADOC_CHECK_NAMES);
184         allChecks.removeAll(INCOMPATIBLE_CHECK_NAMES);
185         allChecks.removeAll(Set.of("Regexp", "RegexpSinglelineJava", "NoCodeInFile"));
186         allChecks.removeAll(NO_VIOLATION_MODULES);
187         allChecks.removeAll(compatibleChecks);
188         allChecks.removeAll(INTERNAL_MODULES);
189 
190         final String format = String.format(Locale.ROOT,
191             "XpathRegressionTest is missing for [%s]."
192                 + " Please add them to src/it/java/org/checkstyle/suppressionxpathfilter",
193             String.join(", ", allChecks));
194         assertWithMessage(format)
195                         .that(allChecks)
196                         .isEmpty();
197     }
198 
199     @Test
200     public void validateInputFiles() throws Exception {
201         try (DirectoryStream<Path> dirs = Files.newDirectoryStream(inputDir, IS_DIRECTORY);
202              Stream<Path> testPathsStream = Files.walk(Path.of(javaDir.toString()))) {
203             final List<Path> testDirs = testPathsStream.filter(Files::isDirectory).toList();
204 
205             for (Path dir : dirs) {
206                 // input directory must be named in lower case
207                 assertWithMessage("%s is not a directory", dir)
208                         .that(Files.isDirectory(dir))
209                         .isTrue();
210                 final String dirName = dir.toFile().getName();
211                 assertWithMessage("Invalid directory name: %s", dirName)
212                         .that(ALLOWED_DIRECTORY_AND_CHECKS.containsKey(dirName)
213                             || isDirNameModuleCategoryName(dirName, testDirs))
214                         .isTrue();
215 
216                 // input directory must be connected to an existing test
217                 final String check = ALLOWED_DIRECTORY_AND_CHECKS.get(dirName);
218                 final Path javaPath = javaDir.resolve("XpathRegression" + check + "Test.java");
219                 assertWithMessage("Input directory '%s' is not connected to Java test case: %s",
220                     dir, javaPath)
221                         .that(Files.exists(javaPath)
222                             || isDirNameModuleCategoryName(dirName, testDirs))
223                         .isTrue();
224 
225                 // input files should be named correctly
226                 validateInputDirectory(dir);
227             }
228         }
229     }
230 
231     private static boolean isDirNameModuleCategoryName(String dirName, List<Path> dirPaths) {
232         return dirPaths.stream().anyMatch(someDir -> someDir.toString().contains(dirName));
233     }
234 
235     private static void validateInputDirectory(Path checkDir) throws IOException {
236         final Pattern pattern = Pattern.compile("^InputXpath(.+)\\.java$");
237         final String check = ALLOWED_DIRECTORY_AND_CHECKS.get(checkDir.toFile().getName());
238 
239         try (DirectoryStream<Path> inputPaths = Files.newDirectoryStream(checkDir)) {
240             for (Path inputPath : inputPaths) {
241                 final String filename = inputPath.toFile().getName();
242                 if (filename.endsWith("java")) {
243                     final Matcher matcher = pattern.matcher(filename);
244                     assertWithMessage(
245                               "Invalid input file '%s', expected pattern:%s", inputPath, pattern)
246                             .that(matcher.matches())
247                             .isTrue();
248 
249                     final String remaining = matcher.group(1);
250                     assertWithMessage("Check name '%s' should be included in input file: %s", check,
251                         inputPath)
252                             .that(remaining)
253                             .startsWith(check);
254                 }
255             }
256         }
257     }
258 
259 }