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