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.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
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
59
60
61
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
82
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
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
150 final Set<Class<?>> abstractJavadocCheckNames = CheckUtil.getCheckstyleChecks()
151 .stream()
152 .filter(AbstractJavadocCheck.class::isAssignableFrom)
153 .collect(Collectors.toCollection(HashSet::new));
154
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
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
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
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
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 }