001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2014  Oliver Burn
004//
005// This library is free software; you can redistribute it and/or
006// modify it under the terms of the GNU Lesser General Public
007// License as published by the Free Software Foundation; either
008// version 2.1 of the License, or (at your option) any later version.
009//
010// This library is distributed in the hope that it will be useful,
011// but WITHOUT ANY WARRANTY; without even the implied warranty of
012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013// Lesser General Public License for more details.
014//
015// You should have received a copy of the GNU Lesser General Public
016// License along with this library; if not, write to the Free Software
017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
018////////////////////////////////////////////////////////////////////////////////
019package com.puppycrawl.tools.checkstyle.checks.coding;
020
021import com.puppycrawl.tools.checkstyle.api.Check;
022import com.puppycrawl.tools.checkstyle.api.DetailAST;
023import com.puppycrawl.tools.checkstyle.api.TokenTypes;
024
025import com.puppycrawl.tools.checkstyle.checks.CheckUtils;
026
027/**
028 * Ensures that the setUp(), tearDown()methods are named correctly,
029 * have no arguments, return void and are either public or protected.
030 * Also ensures that suite() is named correctly, has no arguments, returns
031 * junit.framework.Test, and is public and static.
032 *
033 * Rationale: Developers will often misname one or more of these
034 * methods and not realise that the method is not being called.
035 *
036 * @author <a href="mailto:simon@redhillconsulting.com.au">Simon Harris</a>
037 */
038public final class JUnitTestCaseCheck extends Check
039{
040    /** <code>setUp()</code> method name. */
041    private static final String SET_UP_METHOD_NAME = "setUp";
042    /** <code>tearDown()</code> method name. */
043    private static final String TEAR_DOWN_METHOD_NAME = "tearDown";
044    /** <code>suite()</code> method name. */
045    private static final String SUITE_METHOD_NAME = "suite";
046
047    @Override
048    public int[] getDefaultTokens()
049    {
050        return new int[] {TokenTypes.METHOD_DEF};
051    }
052
053    @Override
054    public int[] getRequiredTokens()
055    {
056        return getDefaultTokens();
057    }
058
059    @Override
060    public void visitToken(DetailAST aAST)
061    {
062        switch (aAST.getType()) {
063        case TokenTypes.METHOD_DEF:
064            visitMethodDef(aAST);
065            break;
066        default:
067            throw new IllegalStateException(aAST.toString());
068        }
069    }
070
071    /**
072     * Checks given method definition.
073     * @param aAST a method def node for check
074     */
075    private void visitMethodDef(DetailAST aAST)
076    {
077        final String name = aAST.findFirstToken(TokenTypes.IDENT).getText();
078
079        if (name.equalsIgnoreCase(SET_UP_METHOD_NAME)) {
080            checkSetUpTearDownMethod(aAST, name, SET_UP_METHOD_NAME);
081        }
082        else if (name.equalsIgnoreCase(TEAR_DOWN_METHOD_NAME)) {
083            checkSetUpTearDownMethod(aAST, name, TEAR_DOWN_METHOD_NAME);
084        }
085        else if (name.equalsIgnoreCase(SUITE_METHOD_NAME)) {
086            checkSuiteMethod(aAST, name);
087        }
088    }
089
090    /**
091     * Checks signature/name of <code>suite()</code>.
092     * @param aAST method definition node
093     * @param aActualName method name
094     */
095    private void checkSuiteMethod(DetailAST aAST, String aActualName)
096    {
097        if (!aActualName.equals(SUITE_METHOD_NAME)) {
098            log(aAST, "junit.method.name", SUITE_METHOD_NAME);
099        }
100
101        if (!isPublicAndStatic(aAST)) {
102            log(aAST, "junit.method.public.and.static", SUITE_METHOD_NAME);
103        }
104
105        // let's check return type
106        final DetailAST typeAST = aAST.findFirstToken(TokenTypes.TYPE);
107        final boolean isArray =
108            (typeAST.findFirstToken(TokenTypes.ARRAY_DECLARATOR) != null);
109        final String type = CheckUtils.createFullType(typeAST).getText();
110        if (isArray
111            || (!"Test".equals(type)
112            && !"junit.framework.Test".equals(type)))
113        {
114            log(aAST, "junit.method.return.type",
115                SUITE_METHOD_NAME, "junit.framework.Test");
116        }
117        checkParameters(aAST, SUITE_METHOD_NAME);
118    }
119
120    /**
121     * Checks signature/name of <code>setUp()</code>/<code>tearDown</code>.
122     * @param aAST method definition node
123     * @param aActualName actual method name
124     * @param aExpectedName expected method name
125     */
126    private void checkSetUpTearDownMethod(DetailAST aAST, String aActualName,
127                                          String aExpectedName)
128    {
129        if (!aActualName.equals(aExpectedName)) {
130            log(aAST, "junit.method.name", aActualName, aExpectedName);
131        }
132
133        if (!isPublicOrProtected(aAST)) {
134            log(aAST, "junit.method.protected.or.public", aExpectedName);
135        }
136
137        if (isStatic(aAST)) {
138            log(aAST, "junit.method.static", aExpectedName);
139        }
140
141        checkReturnValue(aAST, aActualName);
142        checkParameters(aAST, aActualName);
143    }
144
145    /**
146     * Checks that given method returns <code>void</code>.
147     * @param aAST method definition node
148     * @param aName method name
149     */
150    private void checkReturnValue(DetailAST aAST, String aName)
151    {
152        final DetailAST returnValueAST = aAST.findFirstToken(TokenTypes.TYPE);
153
154        if (returnValueAST.findFirstToken(TokenTypes.LITERAL_VOID) == null) {
155            log(aAST, "junit.method.return.type", aName, "void");
156        }
157    }
158
159    /**
160     * Checks return value of given method.
161     * @param aAST method definition node
162     * @param aName method name
163     */
164    private void checkParameters(DetailAST aAST, String aName)
165    {
166        final DetailAST parametersAST =
167            aAST.findFirstToken(TokenTypes.PARAMETERS);
168
169        if (parametersAST.getChildCount() != 0) {
170            log(aAST, "junit.method.parameters", aName);
171        }
172    }
173
174    /**
175     * Checks if given method declared as public or
176     * protected and non-static.
177     * @param aAST method definition node
178     * @return true if given method is declared as public or protected
179     */
180    private boolean isPublicOrProtected(DetailAST aAST)
181    {
182        final DetailAST modifiersAST =
183            aAST.findFirstToken(TokenTypes.MODIFIERS);
184        final DetailAST publicAST =
185            modifiersAST.findFirstToken(TokenTypes.LITERAL_PUBLIC);
186        final DetailAST protectedAST =
187            modifiersAST.findFirstToken(TokenTypes.LITERAL_PROTECTED);
188
189        return (publicAST != null) || (protectedAST != null);
190    }
191
192    /**
193     * Checks if given method declared as <code>public</code> and
194     * <code>static</code>.
195     * @param aAST method definition node
196     * @return true if given method is declared as public and static
197     */
198    private boolean isPublicAndStatic(DetailAST aAST)
199    {
200        final DetailAST modifiersAST =
201            aAST.findFirstToken(TokenTypes.MODIFIERS);
202        final DetailAST publicAST =
203            modifiersAST.findFirstToken(TokenTypes.LITERAL_PUBLIC);
204        final DetailAST staticAST =
205            modifiersAST.findFirstToken(TokenTypes.LITERAL_STATIC);
206
207        return (publicAST != null) && (staticAST != null);
208    }
209
210    /**
211     * Checks if given method declared as static.
212     * @param aAST method definition node
213     * @return true if given method is declared as static
214     */
215    private boolean isStatic(DetailAST aAST)
216    {
217        final DetailAST modifiersAST =
218            aAST.findFirstToken(TokenTypes.MODIFIERS);
219        final DetailAST staticAST =
220            modifiersAST.findFirstToken(TokenTypes.LITERAL_STATIC);
221
222        return (staticAST != null);
223    }
224}