1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2019 the original author or authors.
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  ////////////////////////////////////////////////////////////////////////////////
19  
20  package com.puppycrawl.tools.checkstyle.internal;
21  
22  import java.io.File;
23  import java.io.IOException;
24  import java.nio.file.Files;
25  import java.nio.file.Path;
26  import java.nio.file.Paths;
27  import java.util.ArrayList;
28  import java.util.HashMap;
29  import java.util.List;
30  import java.util.Locale;
31  import java.util.Map;
32  import java.util.function.Consumer;
33  import java.util.stream.Stream;
34  
35  import org.junit.Assert;
36  import org.junit.Test;
37  
38  /**
39   * AllTestsTest.
40   * @noinspection ClassIndependentOfModule
41   */
42  public class AllTestsTest {
43  
44      @Test
45      public void testAllInputsHaveTest() throws Exception {
46          final Map<String, List<String>> allTests = new HashMap<>();
47  
48          walk(Paths.get("src/test/java"), filePath -> {
49              grabAllTests(allTests, filePath.toFile());
50          });
51  
52          Assert.assertTrue("found tests", !allTests.keySet().isEmpty());
53  
54          walk(Paths.get("src/test/resources/com/puppycrawl"), filePath -> {
55              verifyInputFile(allTests, filePath.toFile());
56          });
57          walk(Paths.get("src/test/resources-noncompilable/com/puppycrawl"), filePath -> {
58              verifyInputFile(allTests, filePath.toFile());
59          });
60      }
61  
62      @Test
63      public void testAllTestsHaveProductionCode() throws Exception {
64          final Map<String, List<String>> allTests = new HashMap<>();
65  
66          walk(Paths.get("src/main/java"), filePath -> {
67              grabAllFiles(allTests, filePath.toFile());
68          });
69  
70          Assert.assertTrue("found tests", !allTests.keySet().isEmpty());
71  
72          walk(Paths.get("src/test/java"), filePath -> {
73              verifyHasProductionFile(allTests, filePath.toFile());
74          });
75      }
76  
77      private static void walk(Path path, Consumer<Path> action) throws IOException {
78          try (Stream<Path> walk = Files.walk(path)) {
79              walk.forEach(action);
80          }
81      }
82  
83      private static void grabAllTests(Map<String, List<String>> allTests, File file) {
84          if (file.isFile() && file.getName().endsWith("Test.java")) {
85              String path;
86  
87              try {
88                  path = getSimplePath(file.getCanonicalPath()).replace("CheckTest.java", "")
89                          .replace("Test.java", "");
90              }
91              catch (IOException ex) {
92                  throw new IllegalStateException(ex);
93              }
94  
95              // override for 'AbstractCheck' naming
96              if (path.endsWith(File.separator + "Abstract")) {
97                  path += "Check";
98              }
99  
100             final int slash = path.lastIndexOf(File.separatorChar);
101             final String packge = path.substring(0, slash);
102 
103             List<String> classes = allTests.get(packge);
104 
105             if (classes == null) {
106                 classes = new ArrayList<>();
107 
108                 allTests.put(packge, classes);
109             }
110 
111             classes.add(path.substring(slash + 1));
112         }
113     }
114 
115     private static void grabAllFiles(Map<String, List<String>> allTests, File file) {
116         if (file.isFile()) {
117             final String path;
118 
119             try {
120                 path = getSimplePath(file.getCanonicalPath());
121             }
122             catch (IOException ex) {
123                 throw new IllegalStateException(ex);
124             }
125 
126             final int slash = path.lastIndexOf(File.separatorChar);
127             final String packge = path.substring(0, slash);
128 
129             List<String> classes = allTests.get(packge);
130 
131             if (classes == null) {
132                 classes = new ArrayList<>();
133 
134                 allTests.put(packge, classes);
135             }
136 
137             classes.add(path.substring(slash + 1));
138         }
139     }
140 
141     private static void verifyInputFile(Map<String, List<String>> allTests, File file) {
142         if (file.isFile()) {
143             final String path;
144 
145             try {
146                 path = getSimplePath(file.getCanonicalPath());
147             }
148             catch (IOException ex) {
149                 throw new IllegalStateException(ex);
150             }
151 
152             // until https://github.com/checkstyle/checkstyle/issues/5105
153             if (!path.contains(File.separatorChar + "grammar" + File.separatorChar)
154                     && !path.contains(File.separatorChar + "foo" + File.separatorChar)
155                     && !path.contains(File.separatorChar + "bar" + File.separatorChar)) {
156                 String fileName = file.getName();
157                 final boolean skipFileNaming = shouldSkipInputFileNameCheck(path, fileName);
158 
159                 if (!skipFileNaming) {
160                     Assert.assertTrue("Resource must start with 'Input' or 'Expected': " + path,
161                             fileName.startsWith("Input") || fileName.startsWith("Expected"));
162 
163                     if (fileName.startsWith("Input")) {
164                         fileName = fileName.substring(5);
165                     }
166                     else {
167                         fileName = fileName.substring(8);
168                     }
169 
170                     final int period = fileName.lastIndexOf('.');
171 
172                     if (period > 0) {
173                         fileName = fileName.substring(0, period);
174                     }
175                 }
176 
177                 verifyInputFile(allTests, skipFileNaming, path, fileName);
178             }
179         }
180     }
181 
182     private static void verifyInputFile(Map<String, List<String>> allTests, boolean skipFileNaming,
183             String path, String fileName) {
184         List<String> classes;
185         int slash = path.lastIndexOf(File.separatorChar);
186         String packge = path.substring(0, slash);
187         boolean found = false;
188 
189         for (int depth = 0; depth < 4; depth++) {
190             // -@cs[MoveVariableInsideIf] assignment value is modified later so it can't be
191             // moved
192             final String folderPath = packge;
193             slash = packge.lastIndexOf(File.separatorChar);
194             packge = path.substring(0, slash);
195             classes = allTests.get(packge);
196 
197             if (classes != null
198                     && checkInputMatchCorrectFileStructure(classes, folderPath, skipFileNaming,
199                             fileName)) {
200                 found = true;
201                 break;
202             }
203         }
204 
205         Assert.assertTrue("Resource must be named after a Test like 'InputMyCustomCase.java' "
206                 + "and be in the sub-package of the test like 'mycustom' "
207                 + "for test 'MyCustomCheckTest': " + path, found);
208     }
209 
210     private static void verifyHasProductionFile(Map<String, List<String>> allTests, File file) {
211         if (file.isFile()) {
212             final String fileName = file.getName().replace("Test.java", ".java");
213 
214             if (!fileName.endsWith("TestSupport.java")
215                     // tests external utility XPathEvaluator
216                     && !"XpathMapper.java".equals(fileName)) {
217                 final String path;
218 
219                 try {
220                     path = getSimplePath(file.getCanonicalPath());
221                 }
222                 catch (IOException ex) {
223                     throw new IllegalStateException(ex);
224                 }
225 
226                 if (!path.contains(File.separatorChar + "grammar" + File.separatorChar)
227                         && !path.contains(File.separatorChar + "internal" + File.separatorChar)) {
228                     final int slash = path.lastIndexOf(File.separatorChar);
229                     final String packge = path.substring(0, slash);
230                     final List<String> classes = allTests.get(packge);
231 
232                     Assert.assertTrue("Test must be named after a production class "
233                             + "and must be in the same package of the production class: " + path,
234                             classes != null && classes.contains(fileName));
235                 }
236             }
237         }
238     }
239 
240     private static boolean checkInputMatchCorrectFileStructure(List<String> classes,
241             String folderPath, boolean skipFileNaming, String fileName) {
242         boolean result = false;
243 
244         for (String clss : classes) {
245             if (folderPath.endsWith(File.separatorChar + clss.toLowerCase(Locale.ENGLISH))
246                     && (skipFileNaming || fileName.startsWith(clss))) {
247                 result = true;
248                 break;
249             }
250         }
251 
252         return result;
253     }
254 
255     private static boolean shouldSkipInputFileNameCheck(String path, String fileName) {
256         return "package-info.java".equals(fileName)
257                 || "package.html".equals(fileName)
258                 // special directory for files that can't be renamed or are secondary inputs
259                 || path.contains(File.separatorChar + "inputs" + File.separatorChar)
260                 // all inputs must start with 'messages'
261                 || path.contains(File.separatorChar + "translation" + File.separatorChar);
262     }
263 
264     private static String getSimplePath(String path) {
265         return path.substring(path.lastIndexOf("com" + File.separator + "puppycrawl"));
266     }
267 
268 }