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.beans.PropertyDescriptor;
25 import java.nio.file.Files;
26 import java.nio.file.Path;
27 import java.util.ArrayList;
28 import java.util.Arrays;
29 import java.util.HashSet;
30 import java.util.List;
31 import java.util.Locale;
32 import java.util.Set;
33 import java.util.TreeSet;
34 import java.util.stream.Collectors;
35 import java.util.stream.Stream;
36
37 import org.apache.commons.beanutils.PropertyUtils;
38 import org.junit.jupiter.api.Test;
39
40 import com.puppycrawl.tools.checkstyle.bdd.InlineConfigParser;
41 import com.puppycrawl.tools.checkstyle.bdd.ModuleInputConfiguration;
42 import com.puppycrawl.tools.checkstyle.bdd.TestInputConfiguration;
43 import com.puppycrawl.tools.checkstyle.internal.utils.CheckUtil;
44
45
46
47
48
49
50
51
52 public class AllChecksCompactSourceCoverageTest {
53
54
55 private static final Path NONCOMPILABLE_CHECKS_ROOT = Path.of(
56 "src/test/resources-noncompilable/com/puppycrawl/tools/checkstyle/checks");
57
58
59 private static final String CHECKS_PACKAGE = "com.puppycrawl.tools.checkstyle.checks";
60
61
62 private static final String COMPACT_FOLDER = "compact";
63
64
65 private static final String CHECK_SUFFIX = "Check";
66
67
68
69
70
71 private static final Set<String> COMMON_PROPERTIES = Set.of(
72 "severity",
73 "id",
74 "fileExtensions",
75 "tabWidth",
76 "fileContents",
77 "tokens",
78 "javadocTokens",
79 "violateExecutionOnNonTightHtml"
80 );
81
82
83
84
85
86
87
88 private static final Set<String> SUPPRESSED_CHECKS = Set.of(
89 "AbbreviationAsWordInNameCheck",
90 "AbstractClassNameCheck",
91 "AnnotationLocationCheck",
92 "AnnotationOnSameLineCheck",
93 "AnnotationUseStyleCheck",
94 "AnonInnerLengthCheck",
95 "ArrayTrailingCommaCheck",
96 "ArrayTypeStyleCheck",
97 "AtclauseOrderCheck",
98 "AvoidDoubleBraceInitializationCheck",
99 "AvoidEscapedUnicodeCharactersCheck",
100 "AvoidInlineConditionalsCheck",
101 "AvoidNestedBlocksCheck",
102 "AvoidNoArgumentSuperConstructorCallCheck",
103 "AvoidStarImportCheck",
104 "BooleanExpressionComplexityCheck",
105 "CatchParameterNameCheck",
106 "ClassDataAbstractionCouplingCheck",
107 "ClassTypeParameterNameCheck",
108 "CommentsIndentationCheck",
109 "ConstantNameCheck",
110 "ConstructorsDeclarationGroupingCheck",
111 "CustomImportOrderCheck",
112 "CyclomaticComplexityCheck",
113 "DeclarationOrderCheck",
114 "DefaultComesLastCheck",
115 "DescendantTokenCheck",
116 "DesignForExtensionCheck",
117 "EmptyBlockCheck",
118 "EmptyCatchBlockCheck",
119 "EmptyForInitializerPadCheck",
120 "EmptyForIteratorPadCheck",
121 "EmptyLineSeparatorCheck",
122 "EqualsAvoidNullCheck",
123 "EqualsHashCodeCheck",
124 "ExecutableStatementCountCheck",
125 "ExplicitInitializationCheck",
126 "FallThroughCheck",
127 "FinalClassCheck",
128 "FinalLocalVariableCheck",
129 "FinalParametersCheck",
130 "GenericWhitespaceCheck",
131 "GoogleNonConstantFieldNameCheck",
132 "HexLiteralCaseCheck",
133 "HiddenFieldCheck",
134 "HideUtilityClassConstructorCheck",
135 "IllegalBlockTagCheck",
136 "IllegalCatchCheck",
137 "IllegalIdentifierNameCheck",
138 "IllegalImportCheck",
139 "IllegalInstantiationCheck",
140 "IllegalSymbolCheck",
141 "IllegalThrowsCheck",
142 "IllegalTokenCheck",
143 "IllegalTokenTextCheck",
144 "IllegalTypeCheck",
145 "ImportControlCheck",
146 "ImportOrderCheck",
147 "IndentationCheck",
148 "InnerAssignmentCheck",
149 "InterfaceIsTypeCheck",
150 "InterfaceMemberImpliedModifierCheck",
151 "InterfaceTypeParameterNameCheck",
152 "InvalidJavadocPositionCheck",
153 "JavaNCSSCheck",
154 "JavadocBlockTagLocationCheck",
155 "JavadocContentLocationCheck",
156 "JavadocLeadingAsteriskAlignCheck",
157 "JavadocLinkFirstOccurrenceCheck",
158 "JavadocLinkWellKnownApiCheck",
159 "JavadocMethodCheck",
160 "JavadocMissingLeadingAsteriskCheck",
161 "JavadocMissingWhitespaceAfterAsteriskCheck",
162 "JavadocParagraphCheck",
163 "JavadocStyleCheck",
164 "JavadocTagContinuationIndentationCheck",
165 "JavadocTypeCheck",
166 "JavadocVariableCheck",
167 "LambdaBodyLengthCheck",
168 "LambdaParameterNameCheck",
169 "LeftCurlyCheck",
170 "LocalFinalVariableNameCheck",
171 "LocalVariableNameCheck",
172 "MagicNumberCheck",
173 "MatchXpathCheck",
174 "MemberNameCheck",
175 "MethodCountCheck",
176 "MethodLengthCheck",
177 "MethodNameCheck",
178 "MethodParamPadCheck",
179 "MethodTypeParameterNameCheck",
180 "MissingCtorCheck",
181 "MissingDeprecatedCheck",
182 "MissingJavadocMethodCheck",
183 "MissingJavadocPackageCheck",
184 "MissingJavadocTypeCheck",
185 "MissingNullCaseInSwitchCheck",
186 "MissingOverrideCheck",
187 "MissingOverrideOnRecordAccessorCheck",
188 "MissingSwitchDefaultCheck",
189 "ModifiedControlVariableCheck",
190 "ModifierOrderCheck",
191 "MultipleStringLiteralsCheck",
192 "MultipleVariableDeclarationsCheck",
193 "MutableExceptionCheck",
194 "NPathComplexityCheck",
195 "NestedForDepthCheck",
196 "NestedIfDepthCheck",
197 "NestedTryDepthCheck",
198 "NoArrayTrailingCommaCheck",
199 "NoCloneCheck",
200 "NoCodeInFileCheck",
201 "NoEnumTrailingCommaCheck",
202 "NoLineWrapCheck",
203 "NoWhitespaceAfterCheck",
204 "NoWhitespaceBeforeCaseDefaultColonCheck",
205 "NoWhitespaceBeforeCheck",
206 "NonEmptyAtclauseDescriptionCheck",
207 "NumericalPrefixesInfixesSuffixesCharacterCaseCheck",
208 "OneStatementPerLineCheck",
209 "OneTopLevelClassCheck",
210 "OperatorWrapCheck",
211 "OuterTypeNumberCheck",
212 "OverloadMethodsDeclarationOrderCheck",
213 "PackageAnnotationCheck",
214 "PackageDeclarationCheck",
215 "PackageNameCheck",
216 "ParameterAssignmentCheck",
217 "ParameterNameCheck",
218 "ParameterNumberCheck",
219 "ParenPadCheck",
220 "PatternVariableAssignmentCheck",
221 "PatternVariableNameCheck",
222 "RecordComponentNameCheck",
223 "RecordComponentNumberCheck",
224 "RecordTypeParameterNameCheck",
225 "RedundantImportCheck",
226 "RedundantModifierCheck",
227 "RegexpCheck",
228 "RegexpSinglelineJavaCheck",
229 "RequireEmptyLineBeforeBlockTagGroupCheck",
230 "RequireThisCheck",
231 "ReturnCountCheck",
232 "RightCurlyCheck",
233 "SealedShouldHavePermitsListCheck",
234 "SeparatorWrapCheck",
235 "SimplifyBooleanExpressionCheck",
236 "SimplifyBooleanReturnCheck",
237 "SingleLineJavadocCheck",
238 "SingleSpaceSeparatorCheck",
239 "StaticVariableNameCheck",
240 "SummaryJavadocCheck",
241 "SuperCloneCheck",
242 "SuperFinalizeCheck",
243 "SuppressWarningsCheck",
244 "SuppressWarningsHolder",
245 "TextBlockGoogleStyleFormattingCheck",
246 "ThrowsCountCheck",
247 "TodoCommentCheck",
248 "TrailingCommentCheck",
249 "TypeNameCheck",
250 "TypecastParenPadCheck",
251 "UncommentedMainCheck",
252 "UnnecessaryNullCheckWithInstanceOfCheck",
253 "UnnecessaryParenthesesCheck",
254 "UnnecessarySemicolonAfterOuterTypeDeclarationCheck",
255 "UnnecessarySemicolonAfterTypeMemberDeclarationCheck",
256 "UnnecessarySemicolonInEnumerationCheck",
257 "UnnecessarySemicolonInTryWithResourcesCheck",
258 "UnusedCatchParameterShouldBeUnnamedCheck",
259 "UnusedImportsCheck",
260 "UnusedLambdaParameterShouldBeUnnamedCheck",
261 "UnusedLocalVariableCheck",
262 "UnusedTryResourceShouldBeUnnamedCheck",
263 "UnnecessaryFullyQualifiedTypeCheck",
264 "UpperEllCheck",
265 "UseEnhancedSwitchCheck",
266 "VariableDeclarationUsageDistanceCheck",
267 "WhenShouldBeUsedCheck",
268 "WhitespaceAfterCheck",
269 "WhitespaceAroundCheck"
270 );
271
272 @Test
273 public void testAllChecksHaveCompactSourceCoverage() throws Exception {
274 final List<String> failures = new ArrayList<>();
275
276 for (Class<?> check : CheckUtil.getCheckstyleChecks()) {
277 if (!SUPPRESSED_CHECKS.contains(check.getSimpleName())) {
278 failures.addAll(findCoverageFailures(check));
279 }
280 }
281
282 if (!failures.isEmpty()) {
283 assertWithMessage("The following checks lack compact source input coverage:\n"
284 + String.join("\n", failures)).fail();
285 }
286 }
287
288
289
290
291
292
293
294
295
296
297 private static List<String> findCoverageFailures(Class<?> check) throws Exception {
298 final Path folder = resolveCompactFolder(check);
299 final List<String> failures;
300 if (Files.isDirectory(folder)) {
301 failures = evaluateFolderCoverage(check, folder);
302 }
303 else {
304 failures = List.of(check.getSimpleName()
305 + ": missing compact source input folder '" + folder + "'");
306 }
307 return failures;
308 }
309
310
311
312
313
314
315
316
317
318
319
320
321 private static List<String> evaluateFolderCoverage(Class<?> check, Path folder)
322 throws Exception {
323 final List<Set<String>> nonDefaultsPerInput =
324 collectNonDefaultPropertiesPerInput(folder, check.getName());
325 final Set<String> toExercise = propertiesToExercise(check);
326 final List<String> failures = new ArrayList<>();
327 if (nonDefaultsPerInput.stream().noneMatch(Set::isEmpty)) {
328 final String detail;
329 if (toExercise.isEmpty()) {
330 detail = ": missing a compact source input";
331 }
332 else {
333 detail = ": missing a default-config compact source input "
334 + "(all properties at default)";
335 }
336 failures.add(check.getSimpleName() + detail);
337 }
338 final Set<String> exercised = nonDefaultsPerInput.stream()
339 .flatMap(Set::stream)
340 .collect(Collectors.toCollection(HashSet::new));
341 final Set<String> missing = toExercise.stream()
342 .filter(property -> !exercised.contains(property))
343 .collect(Collectors.toCollection(TreeSet::new));
344 if (!missing.isEmpty()) {
345 failures.add(check.getSimpleName()
346 + ": compact source inputs do not exercise these properties at a "
347 + "non-default value: " + missing);
348 }
349 return failures;
350 }
351
352
353
354
355
356
357
358 private static Path resolveCompactFolder(Class<?> check) {
359 Path folder = NONCOMPILABLE_CHECKS_ROOT;
360 final String packageName = check.getPackageName();
361 if (!CHECKS_PACKAGE.equals(packageName)) {
362 final String subPackage = packageName.substring(CHECKS_PACKAGE.length() + 1);
363 folder = folder.resolve(subPackage.replace('.', '/'));
364 }
365 String simpleName = check.getSimpleName();
366 if (simpleName.endsWith(CHECK_SUFFIX)) {
367 simpleName = simpleName.substring(0, simpleName.length() - CHECK_SUFFIX.length());
368 }
369 return folder.resolve(simpleName.toLowerCase(Locale.ENGLISH)).resolve(COMPACT_FOLDER);
370 }
371
372
373
374
375
376
377
378
379
380
381
382 private static List<Set<String>> collectNonDefaultPropertiesPerInput(
383 Path folder, String checkClassName) throws Exception {
384 final List<Set<String>> nonDefaultsPerInput = new ArrayList<>();
385 final List<Path> inputs;
386 try (Stream<Path> files = Files.list(folder)) {
387 inputs = files
388 .filter(path -> path.getFileName().toString().endsWith(".java"))
389 .toList();
390 }
391 for (Path input : inputs) {
392 final TestInputConfiguration config = InlineConfigParser.parse(input.toString());
393 for (ModuleInputConfiguration module : config.childrenModules()) {
394 if (checkClassName.equals(module.getModuleName())) {
395 nonDefaultsPerInput.add(module.getNonDefaultProperties().keySet());
396 }
397 }
398 }
399 return nonDefaultsPerInput;
400 }
401
402
403
404
405
406
407
408 private static Set<String> propertiesToExercise(Class<?> check) {
409 return Arrays.stream(PropertyUtils.getPropertyDescriptors(check))
410 .filter(descriptor -> descriptor.getWriteMethod() != null)
411 .map(PropertyDescriptor::getName)
412 .filter(property -> !COMMON_PROPERTIES.contains(property))
413 .collect(Collectors.toCollection(TreeSet::new));
414 }
415
416 }