//////////////////////////////////////////////////////////////////////////////// // checkstyle: Checks Java source code for adherence to a set of rules. // Copyright (C) 2001-2017 the original author or authors. // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA //////////////////////////////////////////////////////////////////////////////// package com.github.sevntu.checkstyle.internal; import java.io.File; import java.io.IOException; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.nio.file.Files; import java.nio.file.Paths; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Map.Entry; import java.util.Properties; import java.util.Set; import java.util.TreeMap; import java.util.stream.Collectors; import org.junit.Assert; import org.junit.Test; public class AllChecksTest { @Test public void testAllChecksAreReferencedInConfigFile() throws Exception { final Set<String> checksReferencedInConfig = CheckUtil.getConfigCheckStyleChecks(); final Set<String> checksNames = getFullNames(CheckUtil.getCheckstyleChecks()); checksNames.stream().filter(check -> !checksReferencedInConfig.contains(check)) .forEach(check -> { final String errorMessage = String.format(Locale.ROOT, "%s is not referenced in sevntu-checks.xml", check); Assert.fail(errorMessage); }); } @Test public void testAllCheckstyleModulesHaveTest() throws Exception { for (Class<?> module : CheckUtil.getCheckstyleChecks()) { final String path = "src/test/java/" + module.getName().replace('.', File.separatorChar) + "Test.java"; final File file = new File(path); Assert.assertTrue("Test must exist for " + module.getName() + " and be located at " + path, file.exists()); } } @Test public void testAllCheckstyleModulesHaveMessage() throws Exception { for (Class<?> module : CheckUtil.getCheckstyleChecks()) { Assert.assertFalse(module.getSimpleName() + " should have atleast one 'MSG_*' field for error messages", CheckUtil .getCheckMessages(module).isEmpty()); } } @Test public void testAllCheckstyleMessages() throws Exception { final Map<String, List<String>> usedMessages = new TreeMap<>(); // test validity of messages from checks for (Class<?> module : CheckUtil.getCheckstyleModules()) { for (Field message : CheckUtil.getCheckMessages(module)) { Assert.assertEquals(module.getSimpleName() + "." + message.getName() + " should be 'public static final'", Modifier.PUBLIC | Modifier.STATIC | Modifier.FINAL, message.getModifiers()); // below is required for package/private classes if (!message.isAccessible()) { message.setAccessible(true); } verifyCheckstyleMessage(usedMessages, module, message); } } // test properties for messages not used by checks for (Entry<String, List<String>> entry : usedMessages.entrySet()) { final Properties pr = new Properties(); pr.load(AllChecksTest.class.getResourceAsStream( "/" + entry.getKey().replace('.', '/') + "/messages.properties")); for (Object key : pr.keySet()) { Assert.assertTrue("property '" + key + "' isn't used by any check in package '" + entry.getKey() + "'", entry.getValue().contains(key.toString())); } } } @Test public void testAllInputsHaveTest() throws Exception { final Map<String, List<String>> allTests = new HashMap<>(); Files.walk(Paths.get("src/test/java/com/github/sevntu/checkstyle")) .forEach(filePath -> { grabAllTests(allTests, filePath.toFile()); }); Files.walk(Paths.get("src/test/resources/com/github/sevntu/checkstyle")) .forEach(filePath -> { verifyInputFile(allTests, filePath.toFile()); }); Files.walk(Paths.get("src/test/resources-noncompilable/com/github/sevntu/checkstyle")) .forEach(filePath -> { verifyInputFile(allTests, filePath.toFile()); }); } private static void verifyCheckstyleMessage(Map<String, List<String>> usedMessages, Class<?> module, Field message) throws Exception { final String messageString = message.get(null).toString(); final String packageName = module.getPackage().getName(); List<String> packageMessages = usedMessages.get(packageName); if (packageMessages == null) { packageMessages = new ArrayList<>(); usedMessages.put(packageName, packageMessages); } packageMessages.add(messageString); String result = null; try { result = CheckUtil.getCheckMessage(module, messageString); } catch (IllegalArgumentException ex) { Assert.fail(module.getSimpleName() + " with the message '" + messageString + "' failed with: " + ex.getClass().getSimpleName() + " - " + ex.getMessage()); } Assert.assertNotNull( module.getSimpleName() + " should have text for the message '" + messageString + "'", result); Assert.assertFalse( module.getSimpleName() + " should have non-empty text for the message '" + messageString + "'", result.trim().isEmpty()); Assert.assertFalse( module.getSimpleName() + " should have non-TODO text for the message '" + messageString + "'", result.trim().startsWith("TODO")); } private static void grabAllTests(Map<String, List<String>> allTests, File file) { if (file.isFile() && file.getName().endsWith("Test.java")) { String path = null; try { path = getSimplePath(file.getCanonicalPath()).replace("Test.java", ""); } catch (IOException ex) { throw new IllegalStateException(ex); } final int slash = path.lastIndexOf(File.separatorChar); final String packge = path.substring(0, slash); List<String> classes = allTests.get(packge); if (classes == null) { classes = new ArrayList<String>(); allTests.put(packge, classes); } classes.add(path.substring(slash + 1)); } } private static void verifyInputFile(Map<String, List<String>> allTests, File file) { if (file.isFile()) { String fileName = file.getName().toString(); String path = null; try { path = getSimplePath(file.getCanonicalPath()); } catch (IOException ex) { throw new IllegalStateException(ex); } Assert.assertTrue("Resource must start with 'Input': " + path, fileName.startsWith("Input")); final int period = fileName.lastIndexOf("."); Assert.assertTrue("Resource must have an extension: " + path, period > 0); fileName = fileName.substring(5, period); final int slash = path.lastIndexOf(File.separatorChar); final String packge = path.substring(0, slash); final List<String> classes = allTests.get(packge); if (classes != null || !packge.endsWith("external")) { Assert.assertNotNull("Resource must be in a package that has tests: " + path, classes); boolean found = false; for (String clss : classes) { if (fileName.startsWith(clss)) { found = true; break; } } Assert.assertTrue("Resource must be named after a Test like 'InputMyCheck.java' " + "and be in the same package as the test: " + path, found); } } } /** * Removes 'Check' suffix from each class name in the set. * @param checks class instances. * @return a set of simple names. */ private static Set<String> getFullNames(Set<Class<?>> checks) { return checks.stream().map(check -> check.getName().replace("Check", "")) .collect(Collectors.toSet()); } private static String getSimplePath(String path) { return path.substring(path.lastIndexOf("com" + File.separator + "github")); } }