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