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