diff --git a/eclipsecs-sevntu-plugin/src/com/github/sevntu/checkstyle/checks/design/checkstyle-metadata.properties b/eclipsecs-sevntu-plugin/src/com/github/sevntu/checkstyle/checks/design/checkstyle-metadata.properties index 88a66b4609..ecabd4ae42 100644 --- a/eclipsecs-sevntu-plugin/src/com/github/sevntu/checkstyle/checks/design/checkstyle-metadata.properties +++ b/eclipsecs-sevntu-plugin/src/com/github/sevntu/checkstyle/checks/design/checkstyle-metadata.properties @@ -50,3 +50,8 @@ HideUtilityClassConstructorCheck.desc = Make sure that utility classes (classes InnerClassCheck.name = Inner Class InnerClassCheck.desc = Check nested (internal) classes to be declared at the bottom of the class after all methods (fields) declaration. + +CheckstyleTestMakeupCheck.name = Checkstyle Test Makeup +CheckstyleTestMakeupCheck.desc = Custom check to ensure Checkstyle tests are designed correctly. +CheckstyleTestMakeupCheck.createMethodRegexp = Regular expression for matching a create configuration method by name. +CheckstyleTestMakeupCheck.verifyMethodRegexp = Regular expression for matching a verify method by name. diff --git a/eclipsecs-sevntu-plugin/src/com/github/sevntu/checkstyle/checks/design/checkstyle-metadata.xml b/eclipsecs-sevntu-plugin/src/com/github/sevntu/checkstyle/checks/design/checkstyle-metadata.xml index 15f325fdc0..f31f099f3e 100644 --- a/eclipsecs-sevntu-plugin/src/com/github/sevntu/checkstyle/checks/design/checkstyle-metadata.xml +++ b/eclipsecs-sevntu-plugin/src/com/github/sevntu/checkstyle/checks/design/checkstyle-metadata.xml @@ -160,5 +160,24 @@ + + + %CheckstyleTestMakeupCheck.desc + + + %CheckstyleTestMakeupCheck.createMethodRegexp + + + + %CheckstyleTestMakeupCheck.verifyMethodRegexp + + + + + + + + + diff --git a/sevntu-checks/sevntu-checks.xml b/sevntu-checks/sevntu-checks.xml index 931a3ecc1d..7e624eca0a 100644 --- a/sevntu-checks/sevntu-checks.xml +++ b/sevntu-checks/sevntu-checks.xml @@ -188,6 +188,7 @@ + diff --git a/sevntu-checks/src/main/java/com/github/sevntu/checkstyle/checks/design/CheckstyleTestMakeupCheck.java b/sevntu-checks/src/main/java/com/github/sevntu/checkstyle/checks/design/CheckstyleTestMakeupCheck.java new file mode 100644 index 0000000000..2cc0fc7b3e --- /dev/null +++ b/sevntu-checks/src/main/java/com/github/sevntu/checkstyle/checks/design/CheckstyleTestMakeupCheck.java @@ -0,0 +1,426 @@ +//////////////////////////////////////////////////////////////////////////////// +// 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.checks.design; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.regex.Pattern; + +import com.github.sevntu.checkstyle.Utils; +import com.puppycrawl.tools.checkstyle.api.AbstractCheck; +import com.puppycrawl.tools.checkstyle.api.DetailAST; +import com.puppycrawl.tools.checkstyle.api.TokenTypes; +import com.puppycrawl.tools.checkstyle.utils.AnnotationUtility; +import com.puppycrawl.tools.checkstyle.utils.ScopeUtils; + +/** + *

+ * Custom check to ensure Checkstyle tests are designed correctly. + *

+ * + *

Rationale: This check was made to ensure tests follow a specific design implementation + * so 3rd party utilities like the regression utility can parse the tests for informaiton + * used in creating regression reports. + * + *

+ * Check have following options: + *

+ *
    + *
  • + * createMethodRegexp - Regular expression for matching a create configuration method by name. This + * is the name of the method that starts creating a custom module configuration to be used for + * verifying results for regression purposes. + * Default value is {@code create(Root|Module)Config|getModuleConfig}.
  • + * + *
  • + * verifyMethodRegexp - Regular expression for matching a verify method by name. This is the name + * of the method that verifies the execution results of the custom configuration created for + * regression. As such, it should accept the custom configuration as a parameter. + * Default value is {@code verify(Warns|Suppressed)?}.
  • + *
+ * + *

+ * To configure the check to report incorrectly made checkstyle tests: + *

+ * + *
+ * <module name="CheckstyleTestMakeup"/>
+ * 
+ * + * @author Richard Veach + */ +public class CheckstyleTestMakeupCheck extends AbstractCheck { + /** Violations message. */ + public static final String MSG_KEY_CONFIG_NOT_ASSIGNED = "tester.config.not.assigned"; + /** Violations message. */ + public static final String MSG_KEY_CONFIG_NOT_ASSIGNED_WITH = "tester.config.not.assigned.with"; + /** Violations message. */ + public static final String MSG_KEY_CONFIG_NOT_ASSIGNED_PROPERLY = + "tester.config.not.assigned.properly"; + /** Violations message. */ + public static final String MSG_KEY_UNKNOWN_PROPERTY = "tester.unknown.property"; + /** Violations message. */ + public static final String MSG_KEY_CONFIG_NOT_FOUND = "tester.config.not.found"; + + /** Name of 'getPath' method. */ + private static final String METHOD_GET_PATH = "getPath"; + + /** AST of method that is currently being examined. */ + private DetailAST methodAst; + /** List of variable names that reference a file. */ + private Set fileVariableNames = new HashSet<>(); + /** List of variable names that reference a configuration. */ + private Set checkConfigNames = new HashSet<>(); + /** {@code true} if the 'verify' method was found in the method. */ + private boolean foundVerify; + + /** List of violations generated for a method. */ + private final Map violations = new HashMap<>(); + + /** Regular expression for matching a create method by name. */ + private Pattern createMethodRegexp = Pattern + .compile("create(Root|Module)Config|getModuleConfig"); + + /** Regular expression for matching a verify method by name. */ + private Pattern verifyMethodRegexp = Pattern.compile("verify(Warns|Suppressed)?"); + + /** + * Setter for {@link #createMethodRegexp}. + * @param createMethodRegexp The value to set. + */ + public void setCreateMethodRegexp(Pattern createMethodRegexp) { + this.createMethodRegexp = createMethodRegexp; + } + + /** + * Setter for {@link #verifyMethodRegexp}. + * @param verifyMethodRegexp The value to set. + */ + public void setVerifyMethodRegexp(Pattern verifyMethodRegexp) { + this.verifyMethodRegexp = verifyMethodRegexp; + } + + @Override + public int[] getDefaultTokens() { + return new int[] { + TokenTypes.METHOD_DEF, + TokenTypes.VARIABLE_DEF, + TokenTypes.METHOD_CALL, + }; + } + + @Override + public int[] getAcceptableTokens() { + return getDefaultTokens(); + } + + @Override + public int[] getRequiredTokens() { + return getDefaultTokens(); + } + + @Override + public void beginTree(DetailAST rootAST) { + resetInternalFields(); + } + + @Override + public void visitToken(DetailAST ast) { + switch (ast.getType()) { + case TokenTypes.METHOD_DEF: + checkMethod(ast); + break; + case TokenTypes.VARIABLE_DEF: + checkVariable(ast); + break; + case TokenTypes.METHOD_CALL: + checkMethodCall(ast); + break; + default: + Utils.reportInvalidToken(ast.getType()); + break; + } + } + + /** + * Examines the method to see if it is part of a Test. + * @param ast The method to examine. + */ + private void checkMethod(DetailAST ast) { + if (methodAst == null && AnnotationUtility.containsAnnotation(ast, "Test") + || AnnotationUtility.containsAnnotation(ast, "org.junit.Test")) { + methodAst = ast; + } + } + + /** + * Examines the variable declaration to see if it is a specific variable type to track. + * Variables of type {@link Configuration} or {@link DefaultConfiguration} need to be assigned + * a {@code null}, createModuleConfig, or createRootConfig and is tracked for future purposes. + * Variables of type {@link File} with the modifier {@code final} are tracked for future + * purposes. + * @param ast The variable to examine. + */ + private void checkVariable(DetailAST ast) { + if (methodAst != null && ScopeUtils.isLocalVariableDef(ast)) { + final DetailAST type = ast.findFirstToken(TokenTypes.TYPE).findFirstToken( + TokenTypes.IDENT); + + if (type != null) { + final String typeText = type.getText(); + + if ("DefaultConfiguration".equals(typeText) || "Configuration".equals(typeText)) { + checkConfigurationVariable(ast); + } + else if ("File".equals(typeText) + && ast.findFirstToken(TokenTypes.MODIFIERS) + .findFirstToken(TokenTypes.FINAL) != null) { + fileVariableNames.add(ast.findFirstToken(TokenTypes.IDENT).getText()); + } + } + } + } + + /** + * Examines the configuration variable to see if it is defined as described in + * {@link #checkVariable(DetailAST)}. + * @param ast The variable to examine. + */ + private void checkConfigurationVariable(DetailAST ast) { + checkConfigNames.add(ast.findFirstToken(TokenTypes.IDENT).getText()); + final DetailAST assignment = ast.findFirstToken(TokenTypes.ASSIGN); + + if (assignment == null) { + violations.put(ast, MSG_KEY_CONFIG_NOT_ASSIGNED); + } + else if (assignment.getFirstChild().getFirstChild().getType() == TokenTypes.METHOD_CALL) { + final DetailAST assignmentMethod = assignment.getFirstChild() + .getFirstChild().findFirstToken(TokenTypes.IDENT); + + if (assignmentMethod != null + && !createMethodRegexp.matcher(assignmentMethod.getText()).matches()) { + violations.put(assignment, MSG_KEY_CONFIG_NOT_ASSIGNED_WITH); + } + } + else if (assignment.getFirstChild().getFirstChild().getType() != TokenTypes.LITERAL_NULL) { + violations.put(ast, MSG_KEY_CONFIG_NOT_ASSIGNED_PROPERLY); + } + } + + /** + * Examines the method call and verify it is defined correctly. + * addAttribute method which is called by one of the configurations found earlier, must have + * all it's parameters be acceptable to {@link #isValidMethodCallExpression(DetailAST)}. + * Any method that matches {@link #verifyMethodRegexp} are tracked for future purposes. + * @param ast The method call to examine. + */ + private void checkMethodCall(DetailAST ast) { + if (methodAst != null) { + final DetailAST firstChild = ast.getFirstChild(); + final String methodCallName = getMethodCallName(firstChild); + final String methodCallerName = getMethodCallerName(firstChild); + + if ("addAttribute".equals(methodCallName) + && checkConfigNames.contains(methodCallerName)) { + final DetailAST elist = ast.findFirstToken(TokenTypes.ELIST); + + for (DetailAST expression = elist.getFirstChild(); expression != null; + expression = expression.getNextSibling()) { + if (expression.getType() == TokenTypes.EXPR + && !isValidMethodCallExpression(expression.getFirstChild())) { + violations.put(expression, MSG_KEY_UNKNOWN_PROPERTY); + } + } + } + else if (methodCallerName.equals(methodCallName) + && ast.getParent().getParent().getType() != TokenTypes.METHOD_CALL + && verifyMethodRegexp.matcher(methodCallName).matches()) { + foundVerify = true; + } + } + } + + /** + * Retrieves the name of the method being called. + * @param ast The method call token to examine. + * @return The name of the method. + */ + private String getMethodCallName(DetailAST ast) { + final String result; + if (ast.getType() == TokenTypes.DOT) { + result = getMethodCallName(ast.getFirstChild().getNextSibling()); + } + else { + result = ast.getText(); + } + return result; + } + + /** + * Retrieves the name of the variable calling the method. + * @param ast The method call token to examine. + * @return The name of who is calling the method. + */ + private String getMethodCallerName(DetailAST ast) { + final String result; + if (ast.getType() == TokenTypes.DOT) { + result = getMethodCallName(ast.getFirstChild()); + } + else { + result = ast.getText(); + } + return result; + } + + /** + * Identifies if the parameter of the method call is valid. + * Plain string literal is allowed. + * Adding multiple string literals is allowed because of line length limits. + * Plain {@code null} is allowed due to backward compatibility. + * Method calls are allowed only if they are any form of getPath, converting an enum to a + * string, or retrieving the path of a final {@link File} variable. + * @param expression The expression to examine. + * @return {@code true} if the method call is defined correctly. + */ + private boolean isValidMethodCallExpression(DetailAST expression) { + boolean result = false; + final DetailAST firstChild = expression.getFirstChild(); + + switch (expression.getType()) { + case TokenTypes.STRING_LITERAL: + result = true; + break; + case TokenTypes.METHOD_CALL: + result = isValidMethodCallExpressionMethodCall(firstChild); + break; + case TokenTypes.PLUS: + result = isValidMethodCallExpression(firstChild) + && isValidMethodCallExpression(firstChild.getNextSibling()); + break; + case TokenTypes.LITERAL_NULL: + result = true; + break; + default: + break; + } + + return result; + } + + /** + * Identifies if the inner method call of a method call is valid as defined in + * {@link #isValidMethodCallExpression(DetailAST)}. + * @param firstChild The first child of the method call. + * @return {@code true} if the method call is defined correctly. + */ + private boolean isValidMethodCallExpressionMethodCall(DetailAST firstChild) { + boolean result = false; + + if (firstChild.getType() == TokenTypes.DOT) { + if (firstChild.getFirstChild().getType() == TokenTypes.DOT) { + result = isEnumerationCall(firstChild); + } + else if (isFileVariable(firstChild.getFirstChild())) { + result = true; + } + } + else { + final String methodName = firstChild.getText(); + + if (isMethodGetPath(methodName)) { + result = true; + } + } + + return result; + } + + /** + * Checks if the method call is calling toString, getName, or name on an enumeration. + * @param ast The AST to examine. + * @return {@code true} if the method call is on a enumeration. + */ + private static boolean isEnumerationCall(DetailAST ast) { + boolean result = false; + final DetailAST firstChild = ast.getFirstChild(); + final DetailAST methodCalled = firstChild.getNextSibling(); + final DetailAST parameters = ast.getNextSibling(); + + if (firstChild.getFirstChild().getType() == TokenTypes.IDENT + && ("toString".equals(methodCalled.getText()) + || "getName".equals(methodCalled.getText()) + || "name".equals(methodCalled.getText())) + && parameters.getChildCount() == 0) { + result = true; + } + + return result; + } + + /** + * Checks if the method call is 'getPath' on a {@link File} variable. + * @param firstChild The AST to examine. + * @return {@code true} if the method call is on a file variable. + */ + private boolean isFileVariable(DetailAST firstChild) { + return METHOD_GET_PATH.equals(firstChild.getNextSibling().getText()) + && fileVariableNames.contains(firstChild.getText()); + } + + /** + * Checks if the method name is a form of 'getPath'. + * @param methodName The name to examine. + * @return {@code true} if the method is of the form. + */ + private static boolean isMethodGetPath(String methodName) { + return METHOD_GET_PATH.equals(methodName) + || "getNonCompilablePath".equals(methodName) + || "getUriString".equals(methodName) + || "getResourcePath".equals(methodName); + } + + @Override + public void leaveToken(DetailAST ast) { + if (ast == methodAst) { + if (foundVerify) { + if (checkConfigNames.isEmpty()) { + violations.put(ast, MSG_KEY_CONFIG_NOT_FOUND); + } + + for (Map.Entry entry : violations.entrySet()) { + log(entry.getKey(), entry.getValue()); + } + } + + resetInternalFields(); + } + } + + /** Resets the internal fields when a new file/method is to be processed. */ + private void resetInternalFields() { + methodAst = null; + fileVariableNames.clear(); + checkConfigNames.clear(); + foundVerify = false; + violations.clear(); + } +} diff --git a/sevntu-checks/src/main/resources/com/github/sevntu/checkstyle/checks/design/messages.properties b/sevntu-checks/src/main/resources/com/github/sevntu/checkstyle/checks/design/messages.properties index c480b2c883..d5ecc9e63e 100644 --- a/sevntu-checks/src/main/resources/com/github/sevntu/checkstyle/checks/design/messages.properties +++ b/sevntu-checks/src/main/resources/com/github/sevntu/checkstyle/checks/design/messages.properties @@ -9,3 +9,8 @@ forbid.wildcard.as.return.type=Wildcard as return type should be avoided. public.reference.to.private.type=Reference to the the instance of private type: {0}. static.method.candidate=Method {0} should be declared as static. constructor.without.params=Calls to constructors of ''{0}'' should use at least one parameter. +tester.config.not.assigned=DefaultConfiguration not assigned. +tester.config.not.assigned.with=DefaultConfiguration was not assigned by an allowed create configuration method. +tester.config.not.assigned.properly=DefaultConfiguration was not assigned properly. +tester.unknown.property=Unknown property name/value. +tester.config.not.found=DefaultConfiguration was not found. diff --git a/sevntu-checks/src/test/java/com/github/sevntu/checkstyle/checks/design/CheckstyleTestMakeupCheckTest.java b/sevntu-checks/src/test/java/com/github/sevntu/checkstyle/checks/design/CheckstyleTestMakeupCheckTest.java new file mode 100644 index 0000000000..07eb96291d --- /dev/null +++ b/sevntu-checks/src/test/java/com/github/sevntu/checkstyle/checks/design/CheckstyleTestMakeupCheckTest.java @@ -0,0 +1,122 @@ +//////////////////////////////////////////////////////////////////////////////// +// 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.checks.design; + +import static com.github.sevntu.checkstyle.checks.design.CheckstyleTestMakeupCheck.MSG_KEY_CONFIG_NOT_ASSIGNED; +import static com.github.sevntu.checkstyle.checks.design.CheckstyleTestMakeupCheck.MSG_KEY_CONFIG_NOT_ASSIGNED_PROPERLY; +import static com.github.sevntu.checkstyle.checks.design.CheckstyleTestMakeupCheck.MSG_KEY_CONFIG_NOT_ASSIGNED_WITH; +import static com.github.sevntu.checkstyle.checks.design.CheckstyleTestMakeupCheck.MSG_KEY_CONFIG_NOT_FOUND; +import static com.github.sevntu.checkstyle.checks.design.CheckstyleTestMakeupCheck.MSG_KEY_UNKNOWN_PROPERTY; +import static org.junit.Assert.fail; + +import org.junit.Assert; +import org.junit.Test; + +import com.github.sevntu.checkstyle.BaseCheckTestSupport; +import com.puppycrawl.tools.checkstyle.DefaultConfiguration; +import com.puppycrawl.tools.checkstyle.api.DetailAST; +import com.puppycrawl.tools.checkstyle.api.TokenTypes; + +public class CheckstyleTestMakeupCheckTest extends BaseCheckTestSupport { + @Test + public void testMiscFile() throws Exception { + final DefaultConfiguration checkConfig = + createModuleConfig(CheckstyleTestMakeupCheck.class); + + final String[] expected = {}; + + verify(checkConfig, getPath("InputCheckstyleTestMakeupCheck.java"), expected); + } + + @Test + public void testValidFile() throws Exception { + final DefaultConfiguration checkConfig = + createModuleConfig(CheckstyleTestMakeupCheck.class); + + final String[] expected = {}; + + verify(checkConfig, getPath("InputCheckstyleTestMakeupCheckValid.java"), expected); + } + + @Test + public void testInvalidFile() throws Exception { + final DefaultConfiguration checkConfig = + createModuleConfig(CheckstyleTestMakeupCheck.class); + + final String[] expected = { + "8:5: " + getCheckMessage(MSG_KEY_CONFIG_NOT_FOUND), + "15:9: " + getCheckMessage(MSG_KEY_CONFIG_NOT_ASSIGNED), + "21:37: " + getCheckMessage(MSG_KEY_CONFIG_NOT_ASSIGNED_WITH), + "27:9: " + getCheckMessage(MSG_KEY_CONFIG_NOT_ASSIGNED_PROPERLY), + "36:45: " + getCheckMessage(MSG_KEY_UNKNOWN_PROPERTY), + "37:53: " + getCheckMessage(MSG_KEY_UNKNOWN_PROPERTY), + "38:44: " + getCheckMessage(MSG_KEY_UNKNOWN_PROPERTY), + "39:50: " + getCheckMessage(MSG_KEY_UNKNOWN_PROPERTY), + "40:48: " + getCheckMessage(MSG_KEY_UNKNOWN_PROPERTY), + "41:55: " + getCheckMessage(MSG_KEY_UNKNOWN_PROPERTY), + "42:33: " + getCheckMessage(MSG_KEY_UNKNOWN_PROPERTY), + "43:36: " + getCheckMessage(MSG_KEY_UNKNOWN_PROPERTY), + "44:35: " + getCheckMessage(MSG_KEY_UNKNOWN_PROPERTY), + }; + + verify(checkConfig, getPath("InputCheckstyleTestMakeupCheckInvalid.java"), expected); + } + + @Test + public void testInvalidFileIgnoreVerifies() throws Exception { + final DefaultConfiguration checkConfig = + createModuleConfig(CheckstyleTestMakeupCheck.class); + checkConfig.addAttribute("verifyMethodRegexp", "BAD"); + + final String[] expected = {}; + + verify(checkConfig, getPath("InputCheckstyleTestMakeupCheckInvalid.java"), expected); + } + + @Test + public void testInvalidFileIgnoreCreates() throws Exception { + final DefaultConfiguration checkConfig = + createModuleConfig(CheckstyleTestMakeupCheck.class); + checkConfig.addAttribute("createMethodRegexp", "BAD"); + + final String[] expected = { + "16:36: " + getCheckMessage(MSG_KEY_CONFIG_NOT_ASSIGNED_WITH), + "22:43: " + getCheckMessage(MSG_KEY_CONFIG_NOT_ASSIGNED_WITH), + "28:36: " + getCheckMessage(MSG_KEY_CONFIG_NOT_ASSIGNED_WITH), + }; + + verify(checkConfig, getPath("InputCheckstyleTestMakeupCheckValid.java"), expected); + } + + @Test + public void testInvalidToken() { + final DetailAST parent = new DetailAST(); + parent.setType(TokenTypes.OBJBLOCK); + + final CheckstyleTestMakeupCheck check = new CheckstyleTestMakeupCheck(); + try { + check.visitToken(parent); + fail(); + } + catch (IllegalArgumentException ex) { + Assert.assertEquals("Found unsupported token: OBJBLOCK", ex.getMessage()); + } + } +} diff --git a/sevntu-checks/src/test/resources/com/github/sevntu/checkstyle/checks/design/InputCheckstyleTestMakeupCheck.java b/sevntu-checks/src/test/resources/com/github/sevntu/checkstyle/checks/design/InputCheckstyleTestMakeupCheck.java new file mode 100644 index 0000000000..b1a0ef0f55 --- /dev/null +++ b/sevntu-checks/src/test/resources/com/github/sevntu/checkstyle/checks/design/InputCheckstyleTestMakeupCheck.java @@ -0,0 +1,58 @@ +package com.github.sevntu.checkstyle.checks.design; + +import org.junit.Test; + +public class InputCheckstyleTestMakeupCheck { + private String s; + + public void method1() { + } + + public void method2() { + String s = ""; + method1(); + } + + @Test + public void method3() { + } + + @org.junit.Test + public void method4() { + } + + @Test + public void method5() { + new Thread(new Runnable() { + private String s; + + @Override + public void run() { + } + }); + } + + @Test + public void method6() { + String s = ""; + java.util.List t; + method1(); + InputCheckstyleTestMakeupCheck.test(); + addAttribute("", ""); + test2().test3(); + } + + private static void test() { + } + + private static void addAttribute(String s, String t) { + } + + private InputCheckstyleTestMakeupCheck test2() { + return this; + } + + private InputCheckstyleTestMakeupCheck test3() { + return this; + } +} diff --git a/sevntu-checks/src/test/resources/com/github/sevntu/checkstyle/checks/design/InputCheckstyleTestMakeupCheckInvalid.java b/sevntu-checks/src/test/resources/com/github/sevntu/checkstyle/checks/design/InputCheckstyleTestMakeupCheckInvalid.java new file mode 100644 index 0000000000..be8f62d9a4 --- /dev/null +++ b/sevntu-checks/src/test/resources/com/github/sevntu/checkstyle/checks/design/InputCheckstyleTestMakeupCheckInvalid.java @@ -0,0 +1,87 @@ +package com.github.sevntu.checkstyle.checks.design; + +import java.io.File; + +import org.junit.Test; + +public class InputCheckstyleTestMakeupCheckInvalid { + @Test + public void method1() { + verify(); + } + + @Test + public void method2() { + DefaultConfiguration config; + verify(); + } + + @Test + public void method3() { + DefaultConfiguration config = customCreateConfig(); + verify(); + } + + @Test + public void method4() { + DefaultConfiguration config = new DefaultConfiguration(); + verify(); + } + + @Test + public void method5() { + final Configuration config = createModuleConfig(); + File file = new File(""); + file = new File(""); + config.addAttribute("", file.getPath()); + config.addAttribute("", file.getAbsolutePath()); + config.addAttribute("", customValue()); + config.addAttribute("", ENUM.TEST.getName(0)); + config.addAttribute("", ENUM.TEST.other()); + config.addAttribute("", ENUM.TEST.same.getName(0)); + config.addAttribute("", 0); + config.addAttribute("", "" + 0); + config.addAttribute("", 0 + ""); + verify(); + } + + private static String customValue() { + return null; + } + + private static void verify() { + } + + private DefaultConfiguration customCreateConfig() { + return new DefaultConfiguration(); + } + + private static DefaultConfiguration createModuleConfig() { + return new DefaultConfiguration(); + } + + private static class Configuration { + public void addAttribute(String s, String t) { + } + + public void addAttribute(String s, int i) { + } + } + + private static class DefaultConfiguration extends Configuration { + } + + private enum ENUM { + TEST; + + private static final ENUM same = TEST; + + public String other() { + return name(); + } + + public String getName(int i) { + return name(); + } + } +} diff --git a/sevntu-checks/src/test/resources/com/github/sevntu/checkstyle/checks/design/InputCheckstyleTestMakeupCheckValid.java b/sevntu-checks/src/test/resources/com/github/sevntu/checkstyle/checks/design/InputCheckstyleTestMakeupCheckValid.java new file mode 100644 index 0000000000..f696a159ba --- /dev/null +++ b/sevntu-checks/src/test/resources/com/github/sevntu/checkstyle/checks/design/InputCheckstyleTestMakeupCheckValid.java @@ -0,0 +1,99 @@ +package com.github.sevntu.checkstyle.checks.design; + +import java.io.File; + +import org.junit.Test; + +public class InputCheckstyleTestMakeupCheckValid { + @Test + public void method1() { + final Configuration config = null; + verify(); + } + + @Test + public void method2() { + final Configuration config = createModuleConfig(); + verify(); + } + + @Test + public void method3() { + final DefaultConfiguration config = createRootConfig(); + verifyWarns(); + } + + @Test + public void method4() { + final Configuration config = createModuleConfig(); + config.addAttribute("", null); + config.addAttribute("", ""); + config.addAttribute("", "" + ""); + final File file = new File(""); + config.addAttribute("", file.getPath()); + config.addAttribute("", ENUM.TEST.toString()); + config.addAttribute("", ENUM.TEST.getName()); + config.addAttribute("", ENUM.TEST.name()); + config.addAttribute("", getPath("")); + config.addAttribute("", getNonCompilablePath("")); + config.addAttribute("", getUriString("")); + config.addAttribute("", getResourcePath("")); + verify(); + } + + @Test + public void method5() { + // simulation of PowerMockito.mock(DefaultConfiguration.class); + DefaultConfiguration config = InputCheckstyleTestMakeupCheckValid.createModuleConfig(); + verify(); + } + + private String getPath(String s) { + return s; + } + + private String getNonCompilablePath(String s) { + return s; + } + + private String getUriString(String s) { + return s; + } + + private String getResourcePath(String s) { + return s; + } + + private static void verify() { + } + + private static void verifyWarns() { + } + + private static void verifySuppressed() { + } + + private static DefaultConfiguration createModuleConfig() { + return new DefaultConfiguration(); + } + + private static DefaultConfiguration createRootConfig() { + return new DefaultConfiguration(); + } + + private static class Configuration { + public void addAttribute(String s, String t) { + } + } + + private static class DefaultConfiguration extends Configuration { + } + + private enum ENUM { + TEST; + + public String getName() { + return name(); + } + } +} diff --git a/sevntu-checkstyle-sonar-plugin/src/main/resources/com/github/sevntu/checkstyle/sonar/checkstyle-extensions.xml b/sevntu-checkstyle-sonar-plugin/src/main/resources/com/github/sevntu/checkstyle/sonar/checkstyle-extensions.xml index 8b13317639..978f232515 100644 --- a/sevntu-checkstyle-sonar-plugin/src/main/resources/com/github/sevntu/checkstyle/sonar/checkstyle-extensions.xml +++ b/sevntu-checkstyle-sonar-plugin/src/main/resources/com/github/sevntu/checkstyle/sonar/checkstyle-extensions.xml @@ -904,4 +904,20 @@ Checks if a try/catch block has a junit fail assertion inside the try for a junit method. Checker/TreeWalker/com.github.sevntu.checkstyle.checks.coding.RequireFailForTryCatchInJunitCheck + + + com.github.sevntu.checkstyle.checks.design.CheckstyleTestMakeupCheck + Custom check to ensure Checkstyle tests are designed correctly. + + Custom check to ensure Checkstyle tests are designed correctly. + Checker/TreeWalker/com.github.sevntu.checkstyle.checks.design.CheckstyleTestMakeupCheck + + create(Root|Module)Config|getModuleConfig + Regular expression for matching a create configuration method by name. + + + verify(Warns|Suppressed)? + Regular expression for matching a verify method by name. + +