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