1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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.Arrays;
29 import java.util.HashSet;
30 import java.util.List;
31 import java.util.Locale;
32 import java.util.Map;
33 import java.util.Set;
34 import java.util.function.Function;
35 import java.util.regex.Matcher;
36 import java.util.regex.Pattern;
37 import java.util.stream.Collectors;
38 import java.util.stream.Stream;
39
40 import org.junit.jupiter.api.BeforeEach;
41 import org.junit.jupiter.api.Test;
42
43 import com.google.common.base.Splitter;
44 import com.puppycrawl.tools.checkstyle.AbstractModuleTestSupport;
45 import com.puppycrawl.tools.checkstyle.Definitions;
46 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
47 import com.puppycrawl.tools.checkstyle.checks.javadoc.AbstractJavadocCheck;
48 import com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocMethodCheck;
49 import com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocStyleCheck;
50 import com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocTypeCheck;
51 import com.puppycrawl.tools.checkstyle.checks.javadoc.WriteTagCheck;
52 import com.puppycrawl.tools.checkstyle.internal.utils.CheckUtil;
53
54 public class XpathRegressionTest extends AbstractModuleTestSupport {
55
56
57 public static final Set<String> INCOMPATIBLE_CHECK_NAMES = Set.of(
58 "NoCodeInFile (reason is that AST is not generated for a file not containing code)",
59 "Regexp (reason is at #7759)",
60 "RegexpSinglelineJava (reason is at #7759)"
61 );
62
63
64
65
66
67 public static final Set<String> INCOMPATIBLE_JAVADOC_CHECK_NAMES = Set.of(
68 "AtclauseOrder",
69 "JavadocBlockTagLocation",
70 "JavadocMethod",
71 "JavadocMissingLeadingAsterisk",
72 "JavadocLeadingAsteriskAlign",
73 "JavadocMissingWhitespaceAfterAsterisk",
74 "JavadocParagraph",
75 "JavadocStyle",
76 "JavadocTagContinuationIndentation",
77 "JavadocType",
78 "MissingDeprecated",
79 "NonEmptyAtclauseDescription",
80 "RequireEmptyLineBeforeBlockTagGroup",
81 "SingleLineJavadoc",
82 "SummaryJavadoc",
83 "WriteTag"
84 );
85
86
87
88 private static final Set<Class<?>> REGEXP_JAVADOC_CHECKS = Set.of(
89 JavadocStyleCheck.class,
90 JavadocMethodCheck.class,
91 JavadocTypeCheck.class,
92 WriteTagCheck.class
93 );
94
95
96 private static final Set<String> NO_VIOLATION_MODULES = Set.of(
97 "SuppressWarningsHolder"
98 );
99
100 private static final Set<String> SIMPLE_CHECK_NAMES = getSimpleCheckNames();
101 private static final Map<String, String> ALLOWED_DIRECTORY_AND_CHECKS =
102 getAllowedDirectoryAndChecks();
103
104 private static final Set<String> INTERNAL_MODULES = getInternalModules();
105 private static final DirectoryStream.Filter<Path> IS_DIRECTORY = path
106 -> path.toFile().isDirectory();
107
108 private Path javaDir;
109 private Path inputDir;
110
111 private static Set<String> getSimpleCheckNames() {
112 try {
113 return CheckUtil.getSimpleNames(CheckUtil.getCheckstyleChecks());
114 }
115 catch (IOException exc) {
116 throw new ExceptionInInitializerError(exc);
117 }
118 }
119
120 private static Map<String, String> getAllowedDirectoryAndChecks() {
121 return SIMPLE_CHECK_NAMES
122 .stream()
123 .collect(Collectors.toUnmodifiableMap(
124 id -> id.toLowerCase(Locale.ENGLISH), Function.identity()));
125 }
126
127 private static Set<String> getInternalModules() {
128 return Definitions.INTERNAL_MODULES.stream()
129 .map(moduleName -> {
130 final List<String> packageTokens = Splitter.on(".").splitToList(moduleName);
131 return packageTokens.getLast();
132 })
133 .collect(Collectors.toUnmodifiableSet());
134 }
135
136 @BeforeEach
137 public void setUp() throws Exception {
138 javaDir = Path.of("src/it/java/" + getPackageLocation());
139 inputDir = Path.of(getPath(""));
140 }
141
142 @Override
143 public String getPackageLocation() {
144 return "org/checkstyle/suppressionxpathfilter";
145 }
146
147 @Override
148 protected String getResourceLocation() {
149 return "it";
150 }
151
152 @Test
153 public void validateIncompatibleJavadocCheckNames() throws IOException {
154
155 final Set<Class<?>> abstractJavadocCheckNames = CheckUtil.getCheckstyleChecks()
156 .stream()
157 .filter(AbstractJavadocCheck.class::isAssignableFrom)
158 .filter(XpathRegressionTest::isPureJavadocCheck)
159 .collect(Collectors.toCollection(HashSet::new));
160
161 abstractJavadocCheckNames.addAll(REGEXP_JAVADOC_CHECKS);
162 final Set<String> abstractJavadocCheckSimpleNames =
163 CheckUtil.getSimpleNames(abstractJavadocCheckNames);
164 abstractJavadocCheckSimpleNames.removeAll(INTERNAL_MODULES);
165 assertWithMessage("INCOMPATIBLE_JAVADOC_CHECK_NAMES should contains all descendants "
166 + "of AbstractJavadocCheck")
167 .that(abstractJavadocCheckSimpleNames)
168 .isEqualTo(INCOMPATIBLE_JAVADOC_CHECK_NAMES);
169 }
170
171 @Test
172 public void validateIntegrationTestClassNames() throws Exception {
173 final Set<String> compatibleChecks = new HashSet<>();
174 final Pattern pattern = Pattern.compile("^XpathRegression(.+)Test\\.java$");
175 try (Stream<Path> javaPathsStream = Files.walk(Path.of(javaDir.toString()))) {
176 final List<Path> javaPaths = javaPathsStream.filter(Files::isRegularFile).toList();
177
178 for (Path path : javaPaths) {
179 assertWithMessage("%s is not a regular file", path)
180 .that(Files.isRegularFile(path))
181 .isTrue();
182 final String filename = path.toFile().getName();
183 if (filename.startsWith("Abstract")) {
184 continue;
185 }
186
187 final Matcher matcher = pattern.matcher(filename);
188 assertWithMessage(
189 "Invalid test file: %s, expected pattern: %s", filename, pattern)
190 .that(matcher.matches())
191 .isTrue();
192
193 final String check = matcher.group(1);
194 assertWithMessage("Unknown check '%s' in test file: %s", check, filename)
195 .that(SIMPLE_CHECK_NAMES)
196 .contains(check);
197
198 assertWithMessage(
199 "Check '%s' is now compatible with SuppressionXpathFilter."
200 + " Please update the todo list in"
201 + " XpathRegressionTest.INCOMPATIBLE_CHECK_NAMES", check)
202 .that(INCOMPATIBLE_CHECK_NAMES.contains(check))
203 .isFalse();
204 compatibleChecks.add(check);
205 }
206 }
207
208
209 final Set<String> allChecks = new HashSet<>(SIMPLE_CHECK_NAMES);
210 allChecks.removeAll(INCOMPATIBLE_JAVADOC_CHECK_NAMES);
211 allChecks.removeAll(INCOMPATIBLE_CHECK_NAMES);
212 allChecks.removeAll(Set.of("Regexp", "RegexpSinglelineJava", "NoCodeInFile"));
213 allChecks.removeAll(NO_VIOLATION_MODULES);
214 allChecks.removeAll(compatibleChecks);
215 allChecks.removeAll(INTERNAL_MODULES);
216
217 final String format = String.format(Locale.ROOT,
218 "XpathRegressionTest is missing for [%s]."
219 + " Please add them to src/it/java/org/checkstyle/suppressionxpathfilter",
220 String.join(", ", allChecks));
221 assertWithMessage(format)
222 .that(allChecks)
223 .isEmpty();
224 }
225
226 @Test
227 public void validateInputFiles() throws Exception {
228 try (DirectoryStream<Path> dirs = Files.newDirectoryStream(inputDir, IS_DIRECTORY);
229 Stream<Path> testPathsStream = Files.walk(Path.of(javaDir.toString()))) {
230 final List<Path> testDirs = testPathsStream.filter(Files::isDirectory).toList();
231
232 for (Path dir : dirs) {
233
234 assertWithMessage("%s is not a directory", dir)
235 .that(Files.isDirectory(dir))
236 .isTrue();
237 final String dirName = dir.toFile().getName();
238 assertWithMessage("Invalid directory name: %s", dirName)
239 .that(ALLOWED_DIRECTORY_AND_CHECKS.containsKey(dirName)
240 || isDirNameModuleCategoryName(dirName, testDirs))
241 .isTrue();
242
243
244 final String check = ALLOWED_DIRECTORY_AND_CHECKS.get(dirName);
245 final Path javaPath = javaDir.resolve("XpathRegression" + check + "Test.java");
246 assertWithMessage("Input directory '%s' is not connected to Java test case: %s",
247 dir, javaPath)
248 .that(Files.exists(javaPath)
249 || isDirNameModuleCategoryName(dirName, testDirs))
250 .isTrue();
251
252
253 validateInputDirectory(dir);
254 }
255 }
256 }
257
258 private static boolean isPureJavadocCheck(Class<?> check) {
259 try {
260 final AbstractJavadocCheck instance =
261 (AbstractJavadocCheck) check.getDeclaredConstructor().newInstance();
262 return Arrays.equals(instance.getRequiredTokens(),
263 new int[] {TokenTypes.BLOCK_COMMENT_BEGIN});
264 }
265 catch (ReflectiveOperationException exc) {
266 throw new IllegalStateException("Reflection operation problem for " + check, exc);
267 }
268 }
269
270 private static boolean isDirNameModuleCategoryName(String dirName, List<Path> dirPaths) {
271 return dirPaths.stream().anyMatch(someDir -> someDir.toString().contains(dirName));
272 }
273
274 private static void validateInputDirectory(Path checkDir) throws IOException {
275 final Pattern pattern = Pattern.compile("^InputXpath(.+)\\.java$");
276 final String check = ALLOWED_DIRECTORY_AND_CHECKS.get(checkDir.toFile().getName());
277
278 try (DirectoryStream<Path> inputPaths = Files.newDirectoryStream(checkDir)) {
279 for (Path inputPath : inputPaths) {
280 final String filename = inputPath.toFile().getName();
281 if (filename.endsWith("java")) {
282 final Matcher matcher = pattern.matcher(filename);
283 assertWithMessage(
284 "Invalid input file '%s', expected pattern:%s", inputPath, pattern)
285 .that(matcher.matches())
286 .isTrue();
287
288 final String remaining = matcher.group(1);
289 assertWithMessage("Check name '%s' should be included in input file: %s", check,
290 inputPath)
291 .that(remaining)
292 .startsWith(check);
293 }
294 }
295 }
296 }
297 }