View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2025 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.Locale;
30  import java.util.Map;
31  import java.util.Set;
32  import java.util.function.Function;
33  import java.util.regex.Matcher;
34  import java.util.regex.Pattern;
35  import java.util.stream.Collectors;
36  
37  import org.junit.jupiter.api.BeforeEach;
38  import org.junit.jupiter.api.Test;
39  
40  import com.puppycrawl.tools.checkstyle.AbstractModuleTestSupport;
41  import com.puppycrawl.tools.checkstyle.Definitions;
42  import com.puppycrawl.tools.checkstyle.checks.javadoc.AbstractJavadocCheck;
43  import com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocMethodCheck;
44  import com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocStyleCheck;
45  import com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocTypeCheck;
46  import com.puppycrawl.tools.checkstyle.checks.javadoc.WriteTagCheck;
47  import com.puppycrawl.tools.checkstyle.internal.utils.CheckUtil;
48  
49  public class XpathRegressionTest extends AbstractModuleTestSupport {
50  
51      // Checks that not compatible with SuppressionXpathFilter
52      public static final Set<String> INCOMPATIBLE_CHECK_NAMES = Set.of(
53              "NoCodeInFile (reason is that AST is not generated for a file not containing code)",
54              "Regexp (reason is at  #7759)",
55              "RegexpSinglelineJava (reason is at  #7759)"
56      );
57  
58      // Javadoc checks are not compatible with SuppressionXpathFilter
59      // till https://github.com/checkstyle/checkstyle/issues/5770
60      // then all of them should be added to #INCOMPATIBLE_CHECK_NAMES
61      // and this field should be removed
62      public static final Set<String> INCOMPATIBLE_JAVADOC_CHECK_NAMES = Set.of(
63                      "AtclauseOrder",
64                      "JavadocBlockTagLocation",
65                      "JavadocMethod",
66                      "JavadocMissingLeadingAsterisk",
67                      "JavadocLeadingAsteriskAlign",
68                      "JavadocMissingWhitespaceAfterAsterisk",
69                      "JavadocParagraph",
70                      "JavadocStyle",
71                      "JavadocTagContinuationIndentation",
72                      "JavadocType",
73                      "MissingDeprecated",
74                      "NonEmptyAtclauseDescription",
75                      "RequireEmptyLineBeforeBlockTagGroup",
76                      "SingleLineJavadoc",
77                      "SummaryJavadoc",
78                      "WriteTag"
79      );
80  
81      // Older regex-based checks that are under INCOMPATIBLE_JAVADOC_CHECK_NAMES
82      // but not subclasses of AbstractJavadocCheck.
83      private static final Set<Class<?>> REGEXP_JAVADOC_CHECKS = Set.of(
84                      JavadocStyleCheck.class,
85                      JavadocMethodCheck.class,
86                      JavadocTypeCheck.class,
87                      WriteTagCheck.class
88      );
89  
90      // Modules that will never have xpath support ever because they not report violations
91      private static final Set<String> NO_VIOLATION_MODULES = Set.of(
92              "SuppressWarningsHolder"
93      );
94  
95      private static final Set<String> SIMPLE_CHECK_NAMES = getSimpleCheckNames();
96      private static final Map<String, String> ALLOWED_DIRECTORY_AND_CHECKS =
97          getAllowedDirectoryAndChecks();
98  
99      private static final Set<String> INTERNAL_MODULES = getInternalModules();
100     private static final DirectoryStream.Filter<Path> IS_DIRECTORY = path
101             -> path.toFile().isDirectory();
102 
103     private Path javaDir;
104     private Path inputDir;
105 
106     private static Set<String> getSimpleCheckNames() {
107         try {
108             return CheckUtil.getSimpleNames(CheckUtil.getCheckstyleChecks());
109         }
110         catch (IOException ex) {
111             throw new ExceptionInInitializerError(ex);
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 String[] packageTokens = moduleName.split("\\.");
126                 return packageTokens[packageTokens.length - 1];
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     protected 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 validateIncompatibleJavadocCheckNames() throws IOException {
149         // subclasses of AbstractJavadocCheck
150         final Set<Class<?>> abstractJavadocCheckNames = CheckUtil.getCheckstyleChecks()
151                 .stream()
152                 .filter(AbstractJavadocCheck.class::isAssignableFrom)
153                 .collect(Collectors.toCollection(HashSet::new));
154         // add the extra checks
155         abstractJavadocCheckNames.addAll(REGEXP_JAVADOC_CHECKS);
156         final Set<String> abstractJavadocCheckSimpleNames =
157                 CheckUtil.getSimpleNames(abstractJavadocCheckNames);
158         abstractJavadocCheckSimpleNames.removeAll(INTERNAL_MODULES);
159         assertWithMessage("INCOMPATIBLE_JAVADOC_CHECK_NAMES should contains all descendants "
160                     + "of AbstractJavadocCheck")
161             .that(abstractJavadocCheckSimpleNames)
162             .isEqualTo(INCOMPATIBLE_JAVADOC_CHECK_NAMES);
163     }
164 
165     @Test
166     public void validateIntegrationTestClassNames() throws Exception {
167         final Set<String> compatibleChecks = new HashSet<>();
168         final Pattern pattern = Pattern.compile("^XpathRegression(.+)Test\\.java$");
169         try (DirectoryStream<Path> javaPaths = Files.newDirectoryStream(javaDir)) {
170             for (Path path : javaPaths) {
171                 assertWithMessage(path + " is not a regular file")
172                         .that(Files.isRegularFile(path))
173                         .isTrue();
174                 final String filename = path.toFile().getName();
175                 if (filename.startsWith("Abstract")) {
176                     continue;
177                 }
178 
179                 final Matcher matcher = pattern.matcher(filename);
180                 assertWithMessage(
181                             "Invalid test file: " + filename + ", expected pattern: " + pattern)
182                         .that(matcher.matches())
183                         .isTrue();
184 
185                 final String check = matcher.group(1);
186                 assertWithMessage("Unknown check '" + check + "' in test file: " + filename)
187                         .that(SIMPLE_CHECK_NAMES)
188                         .contains(check);
189 
190                 assertWithMessage(
191                             "Check '" + check + "' is now compatible with SuppressionXpathFilter."
192                                 + " Please update the todo list in"
193                                 + " XpathRegressionTest.INCOMPATIBLE_CHECK_NAMES")
194                         .that(INCOMPATIBLE_CHECK_NAMES.contains(check))
195                         .isFalse();
196                 compatibleChecks.add(check);
197             }
198         }
199 
200         // Ensure that all lists are up-to-date
201         final Set<String> allChecks = new HashSet<>(SIMPLE_CHECK_NAMES);
202         allChecks.removeAll(INCOMPATIBLE_JAVADOC_CHECK_NAMES);
203         allChecks.removeAll(INCOMPATIBLE_CHECK_NAMES);
204         allChecks.removeAll(Set.of("Regexp", "RegexpSinglelineJava", "NoCodeInFile"));
205         allChecks.removeAll(NO_VIOLATION_MODULES);
206         allChecks.removeAll(compatibleChecks);
207         allChecks.removeAll(INTERNAL_MODULES);
208 
209         assertWithMessage("XpathRegressionTest is missing for [" + String.join(", ", allChecks)
210                 + "]. Please add them to src/it/java/org/checkstyle/suppressionxpathfilter")
211                         .that(allChecks)
212                         .isEmpty();
213     }
214 
215     @Test
216     public void validateInputFiles() throws Exception {
217         try (DirectoryStream<Path> dirs = Files.newDirectoryStream(inputDir, IS_DIRECTORY)) {
218             for (Path dir : dirs) {
219                 // input directory must be named in lower case
220                 assertWithMessage(dir + " is not a directory")
221                         .that(Files.isDirectory(dir))
222                         .isTrue();
223                 final String dirName = dir.toFile().getName();
224                 assertWithMessage("Invalid directory name: " + dirName)
225                         .that(ALLOWED_DIRECTORY_AND_CHECKS)
226                         .containsKey(dirName);
227 
228                 // input directory must be connected to an existing test
229                 final String check = ALLOWED_DIRECTORY_AND_CHECKS.get(dirName);
230                 final Path javaPath = javaDir.resolve("XpathRegression" + check + "Test.java");
231                 assertWithMessage("Input directory '" + dir
232                             + "' is not connected to Java test case: " + javaPath)
233                         .that(Files.exists(javaPath))
234                         .isTrue();
235 
236                 // input files should be named correctly
237                 validateInputDirectory(dir);
238             }
239         }
240     }
241 
242     private static void validateInputDirectory(Path checkDir) throws IOException {
243         final Pattern pattern = Pattern.compile("^InputXpath(.+)\\.java$");
244         final String check = ALLOWED_DIRECTORY_AND_CHECKS.get(checkDir.toFile().getName());
245 
246         try (DirectoryStream<Path> inputPaths = Files.newDirectoryStream(checkDir)) {
247             for (Path inputPath : inputPaths) {
248                 final String filename = inputPath.toFile().getName();
249                 if (filename.endsWith("java")) {
250                     final Matcher matcher = pattern.matcher(filename);
251                     assertWithMessage(
252                               "Invalid input file '" + inputPath
253                               + "', expected pattern:" + pattern)
254                             .that(matcher.matches())
255                             .isTrue();
256 
257                     final String remaining = matcher.group(1);
258                     assertWithMessage("Check name '" + check
259                                 + "' should be included in input file: " + inputPath)
260                             .that(remaining)
261                             .startsWith(check);
262                 }
263             }
264         }
265     }
266 }