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                      "JavadocSeeTagOrder",
75                      "JavadocTagContinuationIndentation",
76                      "JavadocThrowsOrder",
77                      "JavadocType",
78                      "JavadocUtilizingTrailingSpace",
79                      "MissingDeprecated",
80                      "NonEmptyAtclauseDescription",
81                      "PreferLiteralJavadocInlineTag",
82                      "PreferCodeOrSnippetJavadocInlineTag",
83                      "RequireEmptyLineBeforeBlockTagGroup",
84                      "SingleLineJavadoc",
85                      "SummaryJavadoc",
86                      "WriteTag"
87      );
88  
89      // Modules that will never have xpath support ever because they not report violations
90      private static final Set<String> NO_VIOLATION_MODULES = Set.of(
91              "SuppressWarningsHolder"
92      );
93  
94      private static final Set<String> SIMPLE_CHECK_NAMES = getSimpleCheckNames();
95      private static final Map<String, String> ALLOWED_DIRECTORY_AND_CHECKS =
96          getAllowedDirectoryAndChecks();
97  
98      private static final Set<String> INTERNAL_MODULES = getInternalModules();
99      private static final DirectoryStream.Filter<Path> IS_DIRECTORY =
100         path -> path.toFile().isDirectory();
101 
102     private Path javaDir;
103     private Path inputDir;
104 
105     private static Set<String> getSimpleCheckNames() {
106         try {
107             return CheckUtil.getSimpleNames(CheckUtil.getCheckstyleChecks());
108         }
109         catch (IOException exc) {
110             throw new IllegalStateException("Failed to get check names, excluding "
111                     + NO_VIOLATION_MODULES, exc);
112         }
113     }
114 
115     private static Map<String, String> getAllowedDirectoryAndChecks() {
116         return SIMPLE_CHECK_NAMES
117             .stream()
118             .collect(Collectors.toUnmodifiableMap(
119                 id -> id.toLowerCase(Locale.ENGLISH), Function.identity()));
120     }
121 
122     private static Set<String> getInternalModules() {
123         return Definitions.INTERNAL_MODULES.stream()
124             .map(moduleName -> {
125                 final List<String> packageTokens = Splitter.on(".").splitToList(moduleName);
126                 return packageTokens.getLast();
127             })
128             .collect(Collectors.toUnmodifiableSet());
129     }
130 
131     @BeforeEach
132     public void setUp() throws Exception {
133         javaDir = Path.of("src/it/java/" + getPackageLocation());
134         inputDir = Path.of(getPath(""));
135     }
136 
137     @Override
138     public String getPackageLocation() {
139         return "org/checkstyle/suppressionxpathfilter";
140     }
141 
142     @Override
143     protected String getResourceLocation() {
144         return "it";
145     }
146 
147     @Test
148     public void validateIntegrationTestClassNames() throws Exception {
149         final Set<String> compatibleChecks = new HashSet<>();
150         final Pattern pattern = Pattern.compile("^XpathRegression(.+)Test\\.java$");
151         try (Stream<Path> javaPathsStream = Files.walk(Path.of(javaDir.toString()))) {
152             final List<Path> javaPaths = javaPathsStream.filter(Files::isRegularFile).toList();
153 
154             for (Path path : javaPaths) {
155                 assertWithMessage("%s is not a regular file", path)
156                         .that(Files.isRegularFile(path))
157                         .isTrue();
158                 final String filename = path.toFile().getName();
159                 if (filename.startsWith("Abstract")) {
160                     continue;
161                 }
162 
163                 final Matcher matcher = pattern.matcher(filename);
164                 assertWithMessage(
165                             "Invalid test file: %s, expected pattern: %s", filename, pattern)
166                         .that(matcher.matches())
167                         .isTrue();
168 
169                 final String check = matcher.group(1);
170                 assertWithMessage("Unknown check '%s' in test file: %s", check, filename)
171                         .that(SIMPLE_CHECK_NAMES)
172                         .contains(check);
173 
174                 assertWithMessage(
175                             "Check '%s' is now compatible with SuppressionXpathFilter."
176                                 + " Please update the todo list in"
177                                 + " XpathRegressionTest.INCOMPATIBLE_CHECK_NAMES", check)
178                         .that(INCOMPATIBLE_CHECK_NAMES.contains(check))
179                         .isFalse();
180                 compatibleChecks.add(check);
181             }
182         }
183 
184         // Ensure that all lists are up-to-date
185         final Set<String> allChecks = new HashSet<>(SIMPLE_CHECK_NAMES);
186         allChecks.removeAll(INCOMPATIBLE_JAVADOC_CHECK_NAMES);
187         allChecks.removeAll(INCOMPATIBLE_CHECK_NAMES);
188         allChecks.removeAll(Set.of("Regexp", "RegexpSinglelineJava", "NoCodeInFile"));
189         allChecks.removeAll(NO_VIOLATION_MODULES);
190         allChecks.removeAll(compatibleChecks);
191         allChecks.removeAll(INTERNAL_MODULES);
192 
193         final String format = String.format(Locale.ROOT,
194             "XpathRegressionTest is missing for [%s]."
195                 + " Please add them to src/it/java/org/checkstyle/suppressionxpathfilter",
196             String.join(", ", allChecks));
197         assertWithMessage(format)
198                         .that(allChecks)
199                         .isEmpty();
200     }
201 
202     @Test
203     public void validateInputFiles() throws Exception {
204         try (DirectoryStream<Path> dirs = Files.newDirectoryStream(inputDir, IS_DIRECTORY);
205              Stream<Path> testPathsStream = Files.walk(Path.of(javaDir.toString()))) {
206             final List<Path> testDirs = testPathsStream.filter(Files::isDirectory).toList();
207 
208             for (Path dir : dirs) {
209                 // input directory must be named in lower case
210                 assertWithMessage("%s is not a directory", dir)
211                         .that(Files.isDirectory(dir))
212                         .isTrue();
213                 final String dirName = dir.toFile().getName();
214                 assertWithMessage("Invalid directory name: %s", dirName)
215                         .that(ALLOWED_DIRECTORY_AND_CHECKS.containsKey(dirName)
216                             || isDirNameModuleCategoryName(dirName, testDirs))
217                         .isTrue();
218 
219                 // input directory must be connected to an existing test
220                 final String check = ALLOWED_DIRECTORY_AND_CHECKS.get(dirName);
221                 final Path javaPath = javaDir.resolve("XpathRegression" + check + "Test.java");
222                 assertWithMessage("Input directory '%s' is not connected to Java test case: %s",
223                     dir, javaPath)
224                         .that(Files.exists(javaPath)
225                             || isDirNameModuleCategoryName(dirName, testDirs))
226                         .isTrue();
227 
228                 // input files should be named correctly
229                 validateInputDirectory(dir);
230             }
231         }
232     }
233 
234     private static boolean isDirNameModuleCategoryName(String dirName, List<Path> dirPaths) {
235         return dirPaths.stream().anyMatch(someDir -> someDir.toString().contains(dirName));
236     }
237 
238     private static void validateInputDirectory(Path checkDir) throws IOException {
239         final Pattern pattern = Pattern.compile("^InputXpath(.+)\\.java$");
240         final String check = ALLOWED_DIRECTORY_AND_CHECKS.get(checkDir.toFile().getName());
241 
242         try (DirectoryStream<Path> inputPaths = Files.newDirectoryStream(checkDir)) {
243             for (Path inputPath : inputPaths) {
244                 final String filename = inputPath.toFile().getName();
245                 if (filename.endsWith("java")) {
246                     final Matcher matcher = pattern.matcher(filename);
247                     assertWithMessage(
248                               "Invalid input file '%s', expected pattern:%s", inputPath, pattern)
249                             .that(matcher.matches())
250                             .isTrue();
251 
252                     final String remaining = matcher.group(1);
253                     assertWithMessage("Check name '%s' should be included in input file: %s", check,
254                         inputPath)
255                             .that(remaining)
256                             .startsWith(check);
257                 }
258             }
259         }
260     }
261 
262 }