Skip to content

Commit

Permalink
Fixes sevntu-checkstyle#83. NameConventionForTestsCheck was implemented
Browse files Browse the repository at this point in the history
  • Loading branch information
denant0 committed Sep 22, 2013
1 parent b29711e commit e37a57c
Show file tree
Hide file tree
Showing 13 changed files with 669 additions and 0 deletions.
Expand Up @@ -62,6 +62,10 @@ MultipleStringLiteralsExtended.ignoreStringsRegexp = Regexp pattern for igno
MultipleStringLiteralsExtended.name = Multiple String Literals Extended
MultipleStringLiteralsExtended.highlightAllDuplicates = Check to highlight all dublicates
NameConvensionForTestsCheck.name = Name Convension For Tests Check
NameConvensionForTestsCheck.desc = This check verifies the name of JUnit test class for compliance with the Convention names. Usually class can be named "Test*", "*Test", "*TestCase" or "*IT" (the latter is for integration tests), but you can provide you own regexp to match 'valid' names of UTs classes.
NameConvensionForTestsCheck.validTestClassNameRegex = Pattern object is used to store the regexp for the names of classes, that could be named "Test*", "*Test", "*TestCase" or "*IT", but you can provide you own regexp to match 'valid' names of UTs classes.
NestedTernaryCheck.name = Nested Ternary Check
NestedTernaryCheck.desc = Highlights the usage of nested ternary operators. <br>Example of nested ternary operator: <br><p><code>final int d = (a == b) ? (a == b) ? 5 : 6 : 6;</code></p>
NestedTernaryCheck.ignoreFinal = Ignore nested ternary operators which are used for assigning the final variables. This option doesn`t ignore nested ternary operators which initializes final variables in constructors.
Expand Down
Expand Up @@ -182,6 +182,15 @@
</property-metadata>
<message-key key="multiple.string.literal" />
</rule-metadata>

<rule-metadata name="%NameConvensionForTestsCheck.name"
internal-name="NameConvensionForTests" parent="TreeWalker">
<alternative-name internal-name="com.github.sevntu.checkstyle.checks.coding.NameConvensionForTestsCheck" />
<description>%NameConvensionForTestsCheck.desc</description>
<property-metadata name="validTestClassNameRegex" datatype="Regex" default-value=".+Test|Test.+|.+IT|.+TestCase">
<description>%NameConvensionForTestsCheck.validTestClassNameRegex</description>
</property-metadata>
</rule-metadata>

<rule-metadata name="%NestedTernaryCheck.name" internal-name="NestedTernary" parent="TreeWalker">
<alternative-name internal-name="com.github.sevntu.checkstyle.checks.coding.NestedTernaryCheck"/>
Expand Down
@@ -0,0 +1,221 @@
////////////////////////////////////////////////////////////////////////////////
// checkstyle: Checks Java source code for adherence to a set of rules.
// Copyright(C) 2001-2012 Oliver Burn
// 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.coding;

import java.util.regex.Pattern;
import com.puppycrawl.tools.checkstyle.api.Check;
import com.puppycrawl.tools.checkstyle.api.DetailAST;
import com.puppycrawl.tools.checkstyle.api.TokenTypes;

/**
* This check verifies the name of JUnit test class for compliance with the
* Convention names. Usually class can be named "Test*", "*Test", "*TestCase" or
* "*IT" (the latter is for integration tests), but you can provide you own
* regexp to match 'valid' names of UTs classes.
* @author <a href="mailto:denant0vz@gmail.com">Denis Antonenkov</a>
*/

public class NameConvensionForTestsCheck extends Check
{
/**
* The key is pointing to the message text String in
* "messages.properties file".
*/
public static final String MSG_KEY = "name.convension.for.tests";
/**
* Pattern object is used to store the regexp for the names of classes, that
* could be named "Test*", "*Test", "*TestCase" or "*IT", but you can
* provide you own regexp to match 'valid' names of UTs classes.
*/
private Pattern mValidTestClassNameRegex = Pattern
.compile(".+Test|Test.+|.+IT|.+TestCase");

/**
* True, if the current class is Junit3 test.
*/
private boolean mIsCurrentClassJUnit3Test;

/**
* True, f the current node is not related to the nodes of the test class.
* Skip processing for the node.
*/
private boolean mSkipCurrentNodeProcessing;

/**
* A current ClassDef AST is being processed by check.
*/
private DetailAST mCurrentClassNode;

/**
* Sets 'valid' class name regexp for Uts.
* @param aValidTestClassNameRegex
* regexp to match 'valid' unit test class names.
*/
public void setValidTestClassNameRegex(
String aValidTestClassNameRegex)
{
if (aValidTestClassNameRegex != null) {
mValidTestClassNameRegex = Pattern
.compile(aValidTestClassNameRegex);
}

}

@Override
public int[] getDefaultTokens()
{
return new int[] {TokenTypes.CLASS_DEF, TokenTypes.METHOD_DEF, };
}

@Override
public void finishTree(DetailAST aRootAST)
{
mCurrentClassNode = null;
mSkipCurrentNodeProcessing = false;
mIsCurrentClassJUnit3Test = false;
}

@Override
public void visitToken(DetailAST aNode)
{
if (!mSkipCurrentNodeProcessing) {
switch (aNode.getType()) {
case TokenTypes.CLASS_DEF:
if (mCurrentClassNode == null) {
mCurrentClassNode = aNode;
mIsCurrentClassJUnit3Test = isExtendsClass(
mCurrentClassNode,
"TestCase");
}
break;
case TokenTypes.METHOD_DEF:
if (hasCurrentClassNodeAsParent(aNode)
&& isJUnitTestMethod(aNode)
&& hasWrongName(mCurrentClassNode))
{
log(mCurrentClassNode.getLineNo(), MSG_KEY,
mValidTestClassNameRegex);
mSkipCurrentNodeProcessing = true;
}
break;
default:
throw new IllegalArgumentException("Node of type "
+ aNode.getType() + " is not supименported");
}
}
}

/**
* Returns true, if the current class extends class with specified
* aClassName.
* @param aClassDefNode
* the node of class definition.
* @param aClassName
* the name of a extended class
* @return True, if class extends class with specified aClassName.
*/
private static boolean isExtendsClass(
final DetailAST aClassDefNode, String aClassName)
{
final DetailAST extendsNode = aClassDefNode
.findFirstToken(TokenTypes.EXTENDS_CLAUSE);
if (extendsNode != null) {
final DetailAST dotNode = extendsNode
.findFirstToken(TokenTypes.DOT);
return dotNode != null
&& aClassName.equals(getIdentText(dotNode))
|| dotNode == null
&& aClassName.equals(getIdentText(extendsNode));
}
return false;
}

/**
* Returns true, if current class is a parent of aMethodDefNode.
* @param aMethodDefNode
* the node of method definition.
* @return True, if current class is a parent of aMethodDefNode.
*/
private boolean hasCurrentClassNodeAsParent(DetailAST aMethodDefNode)
{
return aMethodDefNode.getParent().getParent().equals(mCurrentClassNode);
}

/**
* Returns true, if the current method is JUnit test.
* @param aMethodDefNode
* the node of method definition.
* @return True, if the current method is JUnit test.
*/
private boolean isJUnitTestMethod(DetailAST aMethodDefNode)
{
return isJUnit4Test(aMethodDefNode) || isJUnit3Test(aMethodDefNode);
}

/**
* Returns true, if the class is not the correct name.
* @param aJUnitTestClassDefNode
* the node of class JUnit test.
* @return True, if the class is not the correct name.
*/
private boolean hasWrongName(final DetailAST aJUnitTestClassDefNode)
{
final String className = getIdentText(aJUnitTestClassDefNode);
return !mValidTestClassNameRegex.matcher(className).matches();
}

/**
* Returns true, if the method refers to methods of Junit3 test.
* @param aMethodDefNode
* the node of method definition.
* @return True, if the method refers to methods of JUnit3 test.
*/
private boolean isJUnit3Test(DetailAST aMethodDefNode)
{
return mIsCurrentClassJUnit3Test
&& getIdentText(aMethodDefNode).startsWith("test");
}

/**
* Returns true, if the method refers to methods of JUnit4 test.
* @param aMethodDefNode
* the node of method definition.
* @return True, if the method refers to methods of JUnit4 test.
*/
private static boolean isJUnit4Test(final DetailAST aMethodDefNode)
{
final DetailAST modifiersNode = aMethodDefNode
.findFirstToken(TokenTypes.MODIFIERS);
final DetailAST annotationNode = modifiersNode
.findFirstToken(TokenTypes.ANNOTATION);
return annotationNode != null
&& "Test".equals(getIdentText(annotationNode));
}

/**
* Returns the text identifier for the node containing the identifier.
* @param aNodeWithIdent
* the node containing identifier.
* @return Returns the text identifier for the node containing the
* identifier.
*/
private static String getIdentText(DetailAST aNodeWithIdent)
{
return aNodeWithIdent.findFirstToken(TokenTypes.IDENT).getText();
}
}
Expand Up @@ -60,6 +60,7 @@ modified.control.variable=Control variable ''{0}'' is modified.
multiple.string.literal=The String {0} appears {1} times in the file.
multiple.variable.declarations=Only one variable definition per line allowed.
multiple.variable.declarations.comma=Each variable declaration must be in its own statement.
name.convension.for.tests= JUnit test class name should match ''{0}''.
nested.if.depth=Nested if-else depth is {0,number,integer} (max allowed is {1,number,integer}).
nested.try.depth=Nested try depth is {0,number,integer} (max allowed is {1,number,integer}).
nested.ternary=Nested ternary operator should be avoided.
Expand Down
@@ -0,0 +1,116 @@
////////////////////////////////////////////////////////////////////////////////
// checkstyle: Checks Java source code for adherence to a set of rules.
// Copyright (C) 2001-2013 Oliver Burn
//
// 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.coding;

import java.text.MessageFormat;

import org.junit.Test;

import com.github.sevntu.checkstyle.BaseCheckTestSupport;
import com.puppycrawl.tools.checkstyle.DefaultConfiguration;

/**
* @author <a href="mailto:denant0vz@gmail.com">Denis Antonenkov</a>
*/
public class NameConvensionForTestsCheckTest extends BaseCheckTestSupport
{

private final String msgClass = getCheckMessage(
NameConvensionForTestsCheck.MSG_KEY);

private final DefaultConfiguration mCheckConfig = createCheckConfig(
NameConvensionForTestsCheck.class);

@Test
public void testExtendsTestCase() throws Exception
{
final String[] expected = {};
verify(mCheckConfig,
getPath("InputNameConvensionForTests1.java"),
expected);
}

@Test
public void testClassIsNotTest() throws Exception
{
final String[] expected = {};
verify(mCheckConfig,
getPath("InputNameConvensionForTests2.java"),
expected);

}

@Test
public void testClassJUnit4Test() throws Exception
{
final String[] expected = {};
verify(mCheckConfig,
getPath("InputNameConvensionForTests3.java"),
expected);
}

@Test
public void testEnum() throws Exception
{
final String[] expected = {};
verify(mCheckConfig,
getPath("InputNameConvensionForTests4.java"), expected);
}

@Test
public void testInterface() throws Exception
{
final String[] expected = {};
verify(mCheckConfig,
getPath("InputNameConvensionForTests5.java"), expected);
}

@Test
public void testNestedEnums() throws Exception
{
final String[] expected = {buildMesssage("1",
".+Test|Test.+|.+IT|.+TestCase"), };
verify(mCheckConfig,
getPath("InputNameConvensionForTests6.java"), expected);
}

@Test
public void testAttribute() throws Exception
{
mCheckConfig.addAttribute("validTestClassNameRegex", "Hello*");
final String[] expected = {buildMesssage("15", "Hello*"), };
verify(mCheckConfig,
getPath("InputNameConvensionForTests7.java"),
expected);
}

@Test
public void testQualifiedNameExtends() throws Exception
{
final String[] expected = {};
verify(mCheckConfig,
getPath("InputNameConvensionForTests8.java"),
expected);
}

private String buildMesssage(String aLineNumber, String aArguments)
{
return aLineNumber + ": " + MessageFormat.format(msgClass, aArguments);
}
}
@@ -0,0 +1,20 @@
public class InputNameConvensionForTestsCheckTestExtendsTestCase extends TestCase
{

public void testTest()
{
}

public void testTestCase()
{
}

public void testIT()
{
}

public void testTest()
{
}

}

0 comments on commit e37a57c

Please sign in to comment.