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.File;
25 import java.io.IOException;
26 import java.nio.file.Files;
27 import java.nio.file.Path;
28 import java.nio.file.Paths;
29 import java.util.ArrayList;
30 import java.util.HashMap;
31 import java.util.List;
32 import java.util.Locale;
33 import java.util.Map;
34 import java.util.function.Consumer;
35 import java.util.stream.Stream;
36
37 import org.junit.jupiter.api.Test;
38
39
40
41
42
43
44
45
46 public class AllTestsTest {
47
48 @Test
49 public void testAllInputsHaveTest() throws Exception {
50 final Map<String, List<String>> allTests = new HashMap<>();
51
52 walkVisible(Paths.get("src/test/java"), filePath -> {
53 grabAllTests(allTests, filePath.toFile());
54 });
55
56 assertWithMessage("found tests")
57 .that(allTests.keySet())
58 .isNotEmpty();
59
60 walkVisible(Paths.get("src/test/resources/com/puppycrawl"), filePath -> {
61 verifyInputFile(allTests, filePath.toFile());
62 });
63 walkVisible(Paths.get("src/test/resources-noncompilable/com/puppycrawl"), filePath -> {
64 verifyInputFile(allTests, filePath.toFile());
65 });
66 }
67
68 @Test
69 public void testAllTestsHaveProductionCode() throws Exception {
70 final Map<String, List<String>> allTests = new HashMap<>();
71
72 walkVisible(Paths.get("src/main/java"), filePath -> {
73 grabAllFiles(allTests, filePath.toFile());
74 });
75
76 assertWithMessage("found tests")
77 .that(allTests.keySet())
78 .isNotEmpty();
79
80 walkVisible(Paths.get("src/test/java"), filePath -> {
81 verifyHasProductionFile(allTests, filePath.toFile());
82 });
83 }
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99 private static void walkVisible(Path path, Consumer<Path> action) throws IOException {
100 try (Stream<Path> walk = Files.walk(path)) {
101 walk.filter(filePath -> !filePath.toFile().isHidden())
102 .forEach(action);
103 }
104 }
105
106 private static void grabAllTests(Map<String, List<String>> allTests, File file) {
107 if (file.isFile() && file.getName().endsWith("Test.java")) {
108 String path;
109
110 try {
111 path = getSimplePath(file.getCanonicalPath()).replace("CheckTest.java", "")
112 .replace("Test.java", "");
113 }
114 catch (IOException ex) {
115 throw new IllegalStateException(ex);
116 }
117
118
119 if (path.endsWith(File.separator + "Abstract")) {
120 path += "Check";
121 }
122
123 final int slash = path.lastIndexOf(File.separatorChar);
124 final String packge = path.substring(0, slash);
125 final List<String> classes = allTests.computeIfAbsent(packge, key -> new ArrayList<>());
126
127 classes.add(path.substring(slash + 1));
128 }
129 }
130
131 private static void grabAllFiles(Map<String, List<String>> allTests, File file) {
132 if (file.isFile()) {
133 final String path;
134
135 try {
136 path = getSimplePath(file.getCanonicalPath());
137 }
138 catch (IOException ex) {
139 throw new IllegalStateException(ex);
140 }
141
142 final int slash = path.lastIndexOf(File.separatorChar);
143 final String packge = path.substring(0, slash);
144 final List<String> classes = allTests.computeIfAbsent(packge, key -> new ArrayList<>());
145
146 classes.add(path.substring(slash + 1));
147 }
148 }
149
150 private static void verifyInputFile(Map<String, List<String>> allTests, File file) {
151 if (file.isFile()) {
152 final String path;
153
154 try {
155 path = getSimplePath(file.getCanonicalPath());
156 }
157 catch (IOException ex) {
158 throw new IllegalStateException(ex);
159 }
160
161
162 if (shouldSkipFileProcessing(path)) {
163 String fileName = file.getName();
164 final boolean skipFileNaming = shouldSkipInputFileNameCheck(path, fileName);
165
166 if (!skipFileNaming) {
167 assertWithMessage("Resource must start with 'Input' or 'Expected': " + path)
168 .that(fileName.startsWith("Input") || fileName.startsWith("Expected"))
169 .isTrue();
170
171 if (fileName.startsWith("Input")) {
172 fileName = fileName.substring(5);
173 }
174 else {
175 fileName = fileName.substring(8);
176 }
177
178 final int period = fileName.lastIndexOf('.');
179
180 if (period > 0) {
181 fileName = fileName.substring(0, period);
182 }
183 }
184
185 verifyInputFile(allTests, skipFileNaming, path, fileName);
186 }
187 }
188 }
189
190 private static void verifyInputFile(Map<String, List<String>> allTests, boolean skipFileNaming,
191 String path, String fileName) {
192 List<String> classes;
193 int slash = path.lastIndexOf(File.separatorChar);
194 String packge = path.substring(0, slash);
195 boolean found = false;
196
197 for (int depth = 0; depth < 4; depth++) {
198
199
200 final String folderPath = packge;
201 slash = packge.lastIndexOf(File.separatorChar);
202 packge = path.substring(0, slash);
203 classes = allTests.get(packge);
204
205 if (classes != null
206 && checkInputMatchCorrectFileStructure(classes, folderPath, skipFileNaming,
207 fileName)) {
208 found = true;
209 break;
210 }
211 }
212
213 assertWithMessage("Resource must be named after a Test like 'InputMyCustomCase.java' "
214 + "and be in the sub-package of the test like 'mycustom' "
215 + "for test 'MyCustomCheckTest': " + path)
216 .that(found)
217 .isTrue();
218 }
219
220
221
222
223
224
225
226 private static boolean shouldSkipFileProcessing(String path) {
227 return !path.contains(File.separatorChar + "grammar" + File.separatorChar)
228 && !path.contains(File.separatorChar + "foo" + File.separatorChar)
229 && !path.contains(File.separatorChar + "bar" + File.separatorChar)
230 && !path.contains(File.separator + "abc" + File.separatorChar)
231 && !path.contains(File.separator + "zoo" + File.separatorChar);
232 }
233
234 private static void verifyHasProductionFile(Map<String, List<String>> allTests, File file) {
235 if (file.isFile()) {
236 final String fileName = file.getName().replace("Test.java", ".java");
237
238 if (isTarget(file, fileName)) {
239 final String path;
240
241 try {
242 path = getSimplePath(file.getCanonicalPath());
243 }
244 catch (IOException ex) {
245 throw new IllegalStateException(ex);
246 }
247
248 if (!path.contains(File.separatorChar + "grammar" + File.separatorChar)
249 && !path.contains(File.separatorChar + "internal" + File.separatorChar)) {
250 final int slash = path.lastIndexOf(File.separatorChar);
251 final String packge = path.substring(0, slash);
252 final List<String> classes = allTests.get(packge);
253
254 assertWithMessage("Test must be named after a production class "
255 + "and must be in the same package of the production class: " + path)
256 .that(classes)
257 .contains(fileName);
258 }
259 }
260 }
261 }
262
263 private static boolean isTarget(File file, String fileName) {
264 return !fileName.endsWith("TestSupport.java")
265
266 && !"XpathMapper.java".equals(fileName)
267
268 && !file.getPath().contains("meta")
269
270 && !file.getPath().contains("bdd")
271
272 && !"SuppressForbiddenApi.java".equals(fileName);
273 }
274
275 private static boolean checkInputMatchCorrectFileStructure(List<String> classes,
276 String folderPath, boolean skipFileNaming, String fileName) {
277 boolean result = false;
278
279 for (String clss : classes) {
280 if (folderPath.endsWith(File.separatorChar + clss.toLowerCase(Locale.ENGLISH))
281 && (skipFileNaming || fileName.startsWith(clss))) {
282 result = true;
283 break;
284 }
285 }
286
287 return result;
288 }
289
290 private static boolean shouldSkipInputFileNameCheck(String path, String fileName) {
291 return "package-info.java".equals(fileName)
292 || "package.html".equals(fileName)
293
294 || path.contains(File.separatorChar + "inputs" + File.separatorChar)
295
296 || path.contains(File.separatorChar + "translation" + File.separatorChar);
297 }
298
299 private static String getSimplePath(String path) {
300 return path.substring(path.lastIndexOf("com" + File.separator + "puppycrawl"));
301 }
302
303 }