1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.support.checkstyle;
18 
19 import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
20 import com.puppycrawl.tools.checkstyle.api.DetailAST;
21 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
22 import com.puppycrawl.tools.checkstyle.utils.AnnotationUtility;
23 
24 /**
25  * A check that verifies that all the methods marked with @Test have a matching test size
26  * annotation such as @SmallTest, @MediumTest or @LargeTest. This is needed to make sure
27  * that newly added tests get run in the automated test runner.
28  */
29 public class TestSizeAnnotationCheck extends AbstractCheck {
30     private static final String SMALL_TEST = "SmallTest";
31     private static final String MEDIUM_TEST = "MediumTest";
32     private static final String LARGE_TEST = "LargeTest";
33     private static final String TEST = "Test";
34     private static final String RUN_WITH = "RunWith";
35     private static final String JUNIT4 = "JUnit4";
36 
37     private static final String MESSAGE = "Method with @Test annotation must have a @SmallTest, "
38             + "@MediumTest, or @LargeTest annotation. See https://goo.gl/c2I0WP for recommended "
39             + "timeouts";
40 
41     @Override
getDefaultTokens()42     public int[] getDefaultTokens() {
43         return new int[]{TokenTypes.CLASS_DEF, TokenTypes.INTERFACE_DEF};
44     }
45 
46     @Override
getAcceptableTokens()47     public int[] getAcceptableTokens() {
48         return getDefaultTokens();
49     }
50 
51     @Override
getRequiredTokens()52     public int[] getRequiredTokens() {
53         return getDefaultTokens();
54     }
55 
56     @Override
visitToken(DetailAST ast)57     public void visitToken(DetailAST ast) {
58         if (isJUnit4Runner(ast)) {
59             // Tests that run with JUnit4 do not require test size annotations.
60             return;
61         }
62         final boolean classHasTestSizeAnnotation =
63                 AnnotationUtility.containsAnnotation(ast, SMALL_TEST)
64                         || AnnotationUtility.containsAnnotation(ast, MEDIUM_TEST)
65                         || AnnotationUtility.containsAnnotation(ast, LARGE_TEST);
66 
67         DetailAST objBlock = ast.findFirstToken(TokenTypes.OBJBLOCK);
68         for (DetailAST child = objBlock.getFirstChild();
69                 child != null; child = child.getNextSibling()) {
70             if (child.getType() == TokenTypes.METHOD_DEF
71                     && AnnotationUtility.containsAnnotation(child, TEST)) {
72                 final boolean methodHasTestSizeAnnotation =
73                         AnnotationUtility.containsAnnotation(child, SMALL_TEST)
74                                 || AnnotationUtility.containsAnnotation(child, MEDIUM_TEST)
75                                 || AnnotationUtility.containsAnnotation(child, LARGE_TEST);
76                 if (!classHasTestSizeAnnotation && !methodHasTestSizeAnnotation) {
77                     log(child.getLineNo(), MESSAGE);
78                 }
79             }
80         }
81     }
82 
83     /**
84      * Checks whether this test class is annotated with @RunWith(JUnit4.class).
85      */
isJUnit4Runner(DetailAST ast)86     private boolean isJUnit4Runner(DetailAST ast) {
87         DetailAST runner = AnnotationUtility.getAnnotation(ast, RUN_WITH);
88 
89         // We make an assumption that @RunWith annotation will have this structure:
90         //      @RunWith
91         //         |
92         //        EXPR
93         //          |
94         //    -----DOT-------
95         //  /                \
96         // Runner name     class
97 
98         if (runner == null // There is no @RunWith annotation
99                 || runner.findFirstToken(TokenTypes.EXPR) == null // Annotation has no value.
100                 || runner.findFirstToken(TokenTypes.EXPR).getFirstChild() == null
101                 || runner.findFirstToken(TokenTypes.EXPR).getFirstChild().getFirstChild() == null) {
102             return false;
103         }
104         return JUNIT4.equals(
105                 runner.findFirstToken(TokenTypes.EXPR).getFirstChild().getFirstChild().getText());
106     }
107 }
108