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.io.File;
26 import java.io.IOException;
27 import java.nio.file.Files;
28 import java.nio.file.Path;
29 import java.util.ArrayList;
30 import java.util.Arrays;
31 import java.util.Collections;
32 import java.util.List;
33 import java.util.Map;
34 import java.util.Set;
35 import java.util.stream.Collectors;
36 import java.util.stream.Stream;
37
38 import org.apache.commons.beanutils.PropertyUtils;
39 import org.junit.jupiter.api.Test;
40
41 import com.puppycrawl.tools.checkstyle.AbstractPathTestSupport;
42 import com.puppycrawl.tools.checkstyle.internal.utils.CheckUtil;
43 import com.puppycrawl.tools.checkstyle.internal.utils.XdocUtil;
44
45 public class XdocsExampleFileTest {
46
47 private static final Set<String> COMMON_PROPERTIES = Set.of(
48 "severity",
49 "id",
50 "fileExtensions",
51 "tabWidth",
52 "fileContents",
53 "tokens",
54 "javadocTokens",
55 "violateExecutionOnNonTightHtml"
56 );
57
58
59
60 private static final Map<String, Set<String>> SUPPRESSED_PROPERTIES_BY_CHECK = Map.ofEntries(
61 Map.entry("JavadocStyleCheck", Set.of("endOfSentenceFormat")),
62 Map.entry("SuppressWarningsHolder", Set.of("aliasList")),
63 Map.entry("IndentationCheck", Set.of(
64 "basicOffset",
65 "lineWrappingIndentation",
66 "throwsIndent",
67 "arrayInitIndent",
68 "braceAdjustment"
69 )),
70 Map.entry("ClassMemberImpliedModifierCheck", Set.of(
71 "violateImpliedStaticOnNestedEnum",
72 "violateImpliedStaticOnNestedRecord",
73 "violateImpliedStaticOnNestedInterface"
74 )),
75 Map.entry("InterfaceMemberImpliedModifierCheck", Set.of(
76 "violateImpliedFinalField",
77 "violateImpliedPublicField",
78 "violateImpliedStaticField",
79 "violateImpliedPublicMethod",
80 "violateImpliedAbstractMethod"
81 ))
82 );
83
84 @Test
85 public void testAllCheckPropertiesAreUsedInXdocsExamples() throws Exception {
86 final Map<String, Set<String>> usedPropertiesByCheck =
87 XdocUtil.extractUsedPropertiesFromXdocsExamples();
88 final List<String> failures = new ArrayList<>();
89
90 for (Class<?> checkClass : CheckUtil.getCheckstyleChecks()) {
91 final String checkSimpleName = checkClass.getSimpleName();
92
93 final Set<String> definedProperties = Arrays.stream(
94 PropertyUtils.getPropertyDescriptors(checkClass))
95 .filter(descriptor -> descriptor.getWriteMethod() != null)
96 .map(PropertyDescriptor::getName)
97 .filter(property -> !COMMON_PROPERTIES.contains(property))
98 .collect(Collectors.toUnmodifiableSet());
99
100 final Set<String> usedProperties =
101 usedPropertiesByCheck.getOrDefault(checkSimpleName, Collections.emptySet());
102
103 final Set<String> suppressedProps =
104 SUPPRESSED_PROPERTIES_BY_CHECK.getOrDefault(
105 checkSimpleName, Collections.emptySet());
106
107 for (String property : definedProperties) {
108 if (!usedProperties.contains(property)
109 && !suppressedProps.contains(property)) {
110 failures.add("Missing property in xdoc: '"
111 + property + "' of " + checkSimpleName);
112 }
113 }
114 }
115 if (!failures.isEmpty()) {
116 assertWithMessage("Xdocs are missing properties:\n" + String.join("\n", failures))
117 .fail();
118 }
119 }
120
121 @Test
122 public void testAllExampleFilesHaveCorrespondingTestMethods() throws Exception {
123 final Path examplesResources = Path.of("src/xdocs-examples/resources");
124 final Path examplesNonCompilable = Path.of("src/xdocs-examples/resources-noncompilable");
125 final Path examplesTestRoot = Path.of(
126 "src/xdocs-examples/java/com/puppycrawl/tools/checkstyle/checks");
127 final List<String> failures = new ArrayList<>();
128
129 try (Stream<Path> testFiles = Files.walk(examplesTestRoot)) {
130 testFiles
131 .filter(path -> path.toString().endsWith("ExamplesTest.java"))
132 .forEach(testFile -> {
133 try {
134 scanFile(testFile, examplesResources, examplesNonCompilable, failures);
135 }
136 catch (IOException exception) {
137 throw new IllegalStateException("Error processing: "
138 + testFile, exception);
139 }
140 });
141 }
142 if (!failures.isEmpty()) {
143 assertWithMessage("Example files are missing corresponding test methods:\n"
144 + String.join("\n", failures))
145 .fail();
146 }
147 }
148
149 private static void scanFile(Path testFile, Path examplesResources, Path examplesNonCompilable,
150 List<String> failures)
151 throws IOException {
152 final String testContent = Files.readString(testFile);
153
154 final String className = Path.of("src/xdocs-examples/java").toAbsolutePath()
155 .relativize(testFile.toAbsolutePath()).toString()
156 .replace(File.separator, ".")
157 .replaceFirst("\\.java$", "");
158
159 try {
160 final Class<?> testClass = Class.forName(className);
161 final AbstractPathTestSupport instance = (AbstractPathTestSupport) testClass
162 .getDeclaredConstructor().newInstance();
163 final String packageLocation = instance.getPackageLocation();
164
165 scanExampleDirectory(examplesResources.resolve(packageLocation),
166 testContent, testFile, failures);
167 scanExampleDirectory(examplesNonCompilable.resolve(packageLocation),
168 testContent, testFile, failures);
169 }
170 catch (ReflectiveOperationException exception) {
171 throw new IllegalStateException("Failed to instantiate " + className, exception);
172 }
173 }
174
175 private static void scanExampleDirectory(Path exampleDir, String testContent,
176 Path testFile, List<String> failures) throws IOException {
177 if (Files.exists(exampleDir) && Files.isDirectory(exampleDir)) {
178 try (Stream<Path> exampleFiles = Files.list(exampleDir)) {
179 exampleFiles
180 .filter(path -> {
181 final String fileName = path.getFileName()
182 .toString();
183 return fileName.matches("Example\\d+\\.java");
184 })
185 .forEach(exampleFile -> {
186 final String fileName = exampleFile.getFileName()
187 .toString();
188 if (!testContent.contains("\"" + fileName + "\"")) {
189 failures.add("Missing test for " + fileName + " in "
190 + testFile.getFileName());
191 }
192 });
193 }
194 }
195 }
196 }