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.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.nio.file.Paths;
29  import java.util.HashSet;
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  
38  import org.junit.jupiter.api.BeforeEach;
39  import org.junit.jupiter.api.Test;
40  
41  import com.puppycrawl.tools.checkstyle.AbstractModuleTestSupport;
42  import com.puppycrawl.tools.checkstyle.Definitions;
43  import com.puppycrawl.tools.checkstyle.checks.javadoc.AbstractJavadocCheck;
44  import com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocMethodCheck;
45  import com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocStyleCheck;
46  import com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocTypeCheck;
47  import com.puppycrawl.tools.checkstyle.checks.javadoc.WriteTagCheck;
48  import com.puppycrawl.tools.checkstyle.internal.utils.CheckUtil;
49  
50  public class XpathRegressionTest extends AbstractModuleTestSupport {
51  
52      // Checks that not compatible with SuppressionXpathFilter
53      public static final Set<String> INCOMPATIBLE_CHECK_NAMES = Set.of(
54              "NoCodeInFile (reason is that AST is not generated for a file not containing code)",
55              "Regexp (reason is at  #7759)",
56              "RegexpSinglelineJava (reason is at  #7759)"
57      );
58  
59      // Javadoc checks are not compatible with SuppressionXpathFilter
60      // till https://github.com/checkstyle/checkstyle/issues/5770
61      // then all of them should be added to #INCOMPATIBLE_CHECK_NAMES
62      // and this field should be removed
63      public static final Set<String> INCOMPATIBLE_JAVADOC_CHECK_NAMES = Set.of(
64                      "AtclauseOrder",
65                      "JavadocBlockTagLocation",
66                      "JavadocMethod",
67                      "JavadocMissingLeadingAsterisk",
68                      "JavadocLeadingAsteriskAlign",
69                      "JavadocMissingWhitespaceAfterAsterisk",
70                      "JavadocParagraph",
71                      "JavadocStyle",
72                      "JavadocTagContinuationIndentation",
73                      "JavadocType",
74                      "MissingDeprecated",
75                      "NonEmptyAtclauseDescription",
76                      "RequireEmptyLineBeforeBlockTagGroup",
77                      "SingleLineJavadoc",
78                      "SummaryJavadoc",
79                      "WriteTag"
80      );
81  
82      // Older regex-based checks that are under INCOMPATIBLE_JAVADOC_CHECK_NAMES
83      // but not subclasses of AbstractJavadocCheck.
84      private static final Set<Class<?>> REGEXP_JAVADOC_CHECKS = Set.of(
85                      JavadocStyleCheck.class,
86                      JavadocMethodCheck.class,
87                      JavadocTypeCheck.class,
88                      WriteTagCheck.class
89      );
90  
91      // Checks that allowed to have no XPath IT Regression Testing
92      // till https://github.com/checkstyle/checkstyle/issues/6207
93      private static final Set<String> MISSING_CHECK_NAMES = Set.of(
94              "ClassFanOutComplexity",
95              "ClassTypeParameterName",
96              "DescendantToken",
97              "DesignForExtension",
98              "HideUtilityClassConstructor",
99              "InterfaceTypeParameterName",
100             "LocalVariableName",
101             "MutableException",
102             "RedundantModifier",
103             "SeparatorWrap",
104             "SuperFinalize",
105             "SuppressWarnings");
106 
107     // Modules that will never have xpath support ever because they not report violations
108     private static final Set<String> NO_VIOLATION_MODULES = Set.of(
109             "SuppressWarningsHolder"
110     );
111 
112     private static final Set<String> SIMPLE_CHECK_NAMES = getSimpleCheckNames();
113     private static final Map<String, String> ALLOWED_DIRECTORY_AND_CHECKS =
114         getAllowedDirectoryAndChecks();
115 
116     private static final Set<String> INTERNAL_MODULES = getInternalModules();
117 
118     private Path javaDir;
119     private Path inputDir;
120 
121     private static Set<String> getSimpleCheckNames() {
122         try {
123             return CheckUtil.getSimpleNames(CheckUtil.getCheckstyleChecks());
124         }
125         catch (IOException ex) {
126             throw new ExceptionInInitializerError(ex);
127         }
128     }
129 
130     private static Map<String, String> getAllowedDirectoryAndChecks() {
131         return SIMPLE_CHECK_NAMES
132             .stream()
133             .collect(Collectors.toUnmodifiableMap(
134                 id -> id.toLowerCase(Locale.ENGLISH), Function.identity()));
135     }
136 
137     private static Set<String> getInternalModules() {
138         return Definitions.INTERNAL_MODULES.stream()
139             .map(moduleName -> {
140                 final String[] packageTokens = moduleName.split("\\.");
141                 return packageTokens[packageTokens.length - 1];
142             })
143             .collect(Collectors.toUnmodifiableSet());
144     }
145 
146     @BeforeEach
147     public void setUp() throws Exception {
148         javaDir = Paths.get("src/it/java/" + getPackageLocation());
149         inputDir = Paths.get(getPath(""));
150     }
151 
152     @Override
153     protected String getPackageLocation() {
154         return "org/checkstyle/suppressionxpathfilter";
155     }
156 
157     @Override
158     protected String getResourceLocation() {
159         return "it";
160     }
161 
162     @Test
163     public void validateIncompatibleJavadocCheckNames() throws IOException {
164         // subclasses of AbstractJavadocCheck
165         final Set<Class<?>> abstractJavadocCheckNames = CheckUtil.getCheckstyleChecks()
166                 .stream()
167                 .filter(AbstractJavadocCheck.class::isAssignableFrom)
168                 .collect(Collectors.toCollection(HashSet::new));
169         // add the extra checks
170         abstractJavadocCheckNames.addAll(REGEXP_JAVADOC_CHECKS);
171         final Set<String> abstractJavadocCheckSimpleNames =
172                 CheckUtil.getSimpleNames(abstractJavadocCheckNames);
173         abstractJavadocCheckSimpleNames.removeAll(INTERNAL_MODULES);
174         assertWithMessage("INCOMPATIBLE_JAVADOC_CHECK_NAMES should contains all descendants "
175                     + "of AbstractJavadocCheck")
176             .that(abstractJavadocCheckSimpleNames)
177             .isEqualTo(INCOMPATIBLE_JAVADOC_CHECK_NAMES);
178     }
179 
180     @Test
181     public void validateIntegrationTestClassNames() throws Exception {
182         final Set<String> compatibleChecks = new HashSet<>();
183         final Pattern pattern = Pattern.compile("^XpathRegression(.+)Test\\.java$");
184         try (DirectoryStream<Path> javaPaths = Files.newDirectoryStream(javaDir)) {
185             for (Path path : javaPaths) {
186                 assertWithMessage(path + " is not a regular file")
187                         .that(Files.isRegularFile(path))
188                         .isTrue();
189                 final String filename = path.toFile().getName();
190                 if (filename.startsWith("Abstract")) {
191                     continue;
192                 }
193 
194                 final Matcher matcher = pattern.matcher(filename);
195                 assertWithMessage(
196                             "Invalid test file: " + filename + ", expected pattern: " + pattern)
197                         .that(matcher.matches())
198                         .isTrue();
199 
200                 final String check = matcher.group(1);
201                 assertWithMessage("Unknown check '" + check + "' in test file: " + filename)
202                         .that(SIMPLE_CHECK_NAMES)
203                         .contains(check);
204 
205                 assertWithMessage(
206                             "Check '" + check + "' is now tested. Please update the todo list in"
207                                 + " XpathRegressionTest.MISSING_CHECK_NAMES")
208                         .that(MISSING_CHECK_NAMES.contains(check))
209                         .isFalse();
210                 assertWithMessage(
211                             "Check '" + check + "' is now compatible with SuppressionXpathFilter."
212                                 + " Please update the todo list in"
213                                 + " XpathRegressionTest.INCOMPATIBLE_CHECK_NAMES")
214                         .that(INCOMPATIBLE_CHECK_NAMES.contains(check))
215                         .isFalse();
216                 compatibleChecks.add(check);
217             }
218         }
219 
220         // Ensure that all lists are up-to-date
221         final Set<String> allChecks = new HashSet<>(SIMPLE_CHECK_NAMES);
222         allChecks.removeAll(INCOMPATIBLE_JAVADOC_CHECK_NAMES);
223         allChecks.removeAll(INCOMPATIBLE_CHECK_NAMES);
224         allChecks.removeAll(Set.of("Regexp", "RegexpSinglelineJava", "NoCodeInFile"));
225         allChecks.removeAll(MISSING_CHECK_NAMES);
226         allChecks.removeAll(NO_VIOLATION_MODULES);
227         allChecks.removeAll(compatibleChecks);
228         allChecks.removeAll(INTERNAL_MODULES);
229 
230         assertWithMessage("XpathRegressionTest is missing for [" + String.join(", ", allChecks)
231                 + "]. Please add them to src/it/java/org/checkstyle/suppressionxpathfilter")
232                         .that(allChecks)
233                         .isEmpty();
234     }
235 
236     @Test
237     public void validateInputFiles() throws Exception {
238         try (DirectoryStream<Path> dirs = Files.newDirectoryStream(inputDir)) {
239             for (Path dir : dirs) {
240                 // input directory must be named in lower case
241                 assertWithMessage(dir + " is not a directory")
242                         .that(Files.isDirectory(dir))
243                         .isTrue();
244                 final String dirName = dir.toFile().getName();
245                 assertWithMessage("Invalid directory name: " + dirName)
246                         .that(ALLOWED_DIRECTORY_AND_CHECKS)
247                         .containsKey(dirName);
248 
249                 // input directory must be connected to an existing test
250                 final String check = ALLOWED_DIRECTORY_AND_CHECKS.get(dirName);
251                 final Path javaPath = javaDir.resolve("XpathRegression" + check + "Test.java");
252                 assertWithMessage("Input directory '" + dir
253                             + "' is not connected to Java test case: " + javaPath)
254                         .that(Files.exists(javaPath))
255                         .isTrue();
256 
257                 // input files should be named correctly
258                 validateInputDirectory(dir);
259             }
260         }
261     }
262 
263     private static void validateInputDirectory(Path checkDir) throws IOException {
264         final Pattern pattern = Pattern.compile("^InputXpath(.+)\\.java$");
265         final String check = ALLOWED_DIRECTORY_AND_CHECKS.get(checkDir.toFile().getName());
266 
267         try (DirectoryStream<Path> inputPaths = Files.newDirectoryStream(checkDir)) {
268             for (Path inputPath : inputPaths) {
269                 final String filename = inputPath.toFile().getName();
270                 if (filename.endsWith("java")) {
271                     final Matcher matcher = pattern.matcher(filename);
272                     assertWithMessage(
273                               "Invalid input file '" + inputPath
274                               + "', expected pattern:" + pattern)
275                             .that(matcher.matches())
276                             .isTrue();
277 
278                     final String remaining = matcher.group(1);
279                     assertWithMessage("Check name '" + check
280                                 + "' should be included in input file: " + inputPath)
281                             .that(remaining)
282                             .startsWith(check);
283                 }
284             }
285         }
286     }
287 }