1 /* 2 * Copyright (C) 2016 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 package com.android.tradefed.util; 17 18 import com.android.tradefed.log.LogUtil.CLog; 19 20 import org.junit.runner.Description; 21 22 import java.lang.annotation.Annotation; 23 import java.lang.reflect.AnnotatedElement; 24 import java.lang.reflect.Method; 25 import java.util.Arrays; 26 import java.util.Collection; 27 import java.util.HashSet; 28 import java.util.Set; 29 30 /** 31 * Helper class for filtering tests 32 */ 33 public class TestFilterHelper { 34 35 /** The include filters of the test name to run */ 36 private Set<String> mIncludeFilters = new HashSet<>(); 37 38 /** The exclude filters of the test name to run */ 39 private Set<String> mExcludeFilters = new HashSet<>(); 40 41 /** The include annotations of the test to run */ 42 private Set<String> mIncludeAnnotations = new HashSet<>(); 43 44 /** The exclude annotations of the test to run */ 45 private Set<String> mExcludeAnnotations = new HashSet<>(); 46 TestFilterHelper()47 public TestFilterHelper() { 48 } 49 TestFilterHelper(Collection<String> includeFilters, Collection<String> excludeFilters, Collection<String> includeAnnotation, Collection<String> excludeAnnotation)50 public TestFilterHelper(Collection<String> includeFilters, Collection<String> excludeFilters, 51 Collection<String> includeAnnotation, Collection<String> excludeAnnotation) { 52 mIncludeFilters.addAll(includeFilters); 53 mExcludeFilters.addAll(excludeFilters); 54 mIncludeAnnotations.addAll(includeAnnotation); 55 mExcludeAnnotations.addAll(excludeAnnotation); 56 } 57 58 /** 59 * Adds a filter of which tests to include 60 */ addIncludeFilter(String filter)61 public void addIncludeFilter(String filter) { 62 mIncludeFilters.add(filter); 63 } 64 65 /** 66 * Adds the {@link Set} of filters of which tests to include 67 */ addAllIncludeFilters(Set<String> filters)68 public void addAllIncludeFilters(Set<String> filters) { 69 mIncludeFilters.addAll(filters); 70 } 71 72 /** 73 * Adds a filter of which tests to exclude 74 */ addExcludeFilter(String filter)75 public void addExcludeFilter(String filter) { 76 mExcludeFilters.add(filter); 77 } 78 79 /** 80 * Adds the {@link Set} of filters of which tests to exclude. 81 */ addAllExcludeFilters(Set<String> filters)82 public void addAllExcludeFilters(Set<String> filters) { 83 mExcludeFilters.addAll(filters); 84 } 85 86 /** 87 * Adds an include annotation of the test to run 88 */ addIncludeAnnotation(String annotation)89 public void addIncludeAnnotation(String annotation) { 90 mIncludeAnnotations.add(annotation); 91 } 92 93 /** 94 * Adds the {@link Set} of include annotation of the test to run 95 */ addAllIncludeAnnotation(Set<String> annotations)96 public void addAllIncludeAnnotation(Set<String> annotations) { 97 mIncludeAnnotations.addAll(annotations); 98 } 99 100 /** 101 * Adds an exclude annotation of the test to run 102 */ addExcludeAnnotation(String notAnnotation)103 public void addExcludeAnnotation(String notAnnotation) { 104 mExcludeAnnotations.add(notAnnotation); 105 } 106 107 /** 108 * Adds the {@link Set} of exclude annotation of the test to run 109 */ addAllExcludeAnnotation(Set<String> notAnnotations)110 public void addAllExcludeAnnotation(Set<String> notAnnotations) { 111 mExcludeAnnotations.addAll(notAnnotations); 112 } 113 getIncludeFilters()114 public Set<String> getIncludeFilters() { 115 return mIncludeFilters; 116 } 117 getExcludeFilters()118 public Set<String> getExcludeFilters() { 119 return mExcludeFilters; 120 } 121 getIncludeAnnotation()122 public Set<String> getIncludeAnnotation() { 123 return mIncludeAnnotations; 124 } 125 getExcludeAnnotation()126 public Set<String> getExcludeAnnotation() { 127 return mExcludeAnnotations; 128 } 129 130 131 /** 132 * Check if an element that has annotation passes the filter 133 * 134 * @param annotatedElement the element to filter 135 * @return true if the test should run, false otherwise 136 */ shouldTestRun(AnnotatedElement annotatedElement)137 public boolean shouldTestRun(AnnotatedElement annotatedElement) { 138 return shouldTestRun(Arrays.asList(annotatedElement.getAnnotations())); 139 } 140 141 /** 142 * Check if the {@link Description} that contains annotations passes the filter 143 * 144 * @param desc the element to filter 145 * @return true if the test should run, false otherwise 146 */ shouldTestRun(Description desc)147 public boolean shouldTestRun(Description desc) { 148 return shouldTestRun(desc.getAnnotations()); 149 } 150 151 /** 152 * Internal helper to determine if a particular test should run based on its annotations. 153 */ shouldTestRun(Collection<Annotation> annotationsList)154 private boolean shouldTestRun(Collection<Annotation> annotationsList) { 155 if (isExcluded(annotationsList)) { 156 return false; 157 } 158 return isIncluded(annotationsList); 159 } 160 isExcluded(Collection<Annotation> annotationsList)161 private boolean isExcluded(Collection<Annotation> annotationsList) { 162 if (!mExcludeAnnotations.isEmpty()) { 163 for (Annotation a : annotationsList) { 164 if (mExcludeAnnotations.contains(a.annotationType().getName())) { 165 // If any of the method annotation match an ExcludeAnnotation, don't run it 166 CLog.i("Skipping %s, ExcludeAnnotation exclude it", a); 167 return true; 168 } 169 } 170 } 171 return false; 172 } 173 isIncluded(Collection<Annotation> annotationsList)174 private boolean isIncluded(Collection<Annotation> annotationsList) { 175 if (!mIncludeAnnotations.isEmpty()) { 176 Set<String> neededAnnotation = new HashSet<String>(); 177 neededAnnotation.addAll(mIncludeAnnotations); 178 for (Annotation a : annotationsList) { 179 if (neededAnnotation.contains(a.annotationType().getName())) { 180 neededAnnotation.remove(a.annotationType().getName()); 181 } 182 } 183 if (neededAnnotation.size() != 0) { 184 // The test needs to have all the include annotation to pass. 185 CLog.i("Skipping, IncludeAnnotation filtered it"); 186 return false; 187 } 188 } 189 return true; 190 } 191 192 /** 193 * Check if an element that has annotation passes the filter 194 * 195 * @param packageName name of the method's package 196 * @param classObj method's class 197 * @param method test method 198 * @return true if the test method should run, false otherwise 199 */ shouldRun(String packageName, Class<?> classObj, Method method)200 public boolean shouldRun(String packageName, Class<?> classObj, Method method) { 201 String className = classObj.getName(); 202 String methodName = String.format("%s#%s", className, method.getName()); 203 if (!shouldRunFilter(packageName, className, methodName)) { 204 return false; 205 } 206 // If class is explicitly annotated to be excluded. 207 if (isExcluded(Arrays.asList(classObj.getAnnotations()))) { 208 return false; 209 } 210 // if class include but method exclude, we exclude 211 if (isIncluded(Arrays.asList(classObj.getAnnotations())) 212 && isExcluded(Arrays.asList(method.getAnnotations()))) { 213 return false; 214 } 215 // If a class is explicitly included and check above says method could run, we skip method 216 // check, it will be included. 217 if (mIncludeAnnotations.isEmpty() 218 || !isIncluded(Arrays.asList(classObj.getAnnotations()))) { 219 if (!shouldTestRun(method)) { 220 return false; 221 } 222 } 223 return mIncludeFilters.isEmpty() 224 || mIncludeFilters.contains(methodName) 225 || mIncludeFilters.contains(className) 226 || mIncludeFilters.contains(packageName); 227 } 228 229 /** 230 * Check if an element that has annotation passes the filter 231 * 232 * @param desc a {@link Description} that describes the test. 233 * @return true if the test method should run, false otherwise 234 */ shouldRun(Description desc)235 public boolean shouldRun(Description desc) { 236 // We need to build the packageName for a description object 237 Class<?> classObj = null; 238 try { 239 classObj = Class.forName(desc.getClassName()); 240 } catch (ClassNotFoundException e) { 241 throw new IllegalArgumentException(String.format("Could not load Test class %s", 242 classObj), e); 243 } 244 String packageName = classObj.getPackage().getName(); 245 246 String className = desc.getClassName(); 247 String methodName = String.format("%s#%s", className, desc.getMethodName()); 248 if (!shouldRunFilter(packageName, className, methodName)) { 249 return false; 250 } 251 if (!shouldTestRun(desc)) { 252 return false; 253 } 254 return mIncludeFilters.isEmpty() 255 || mIncludeFilters.contains(methodName) 256 || mIncludeFilters.contains(className) 257 || mIncludeFilters.contains(packageName); 258 } 259 260 /** 261 * Internal helper to check if a particular test should run based on its package, class, method 262 * names. 263 */ shouldRunFilter(String packageName, String className, String methodName)264 private boolean shouldRunFilter(String packageName, String className, String methodName) { 265 if (mExcludeFilters.contains(packageName)) { 266 // Skip package because it was excluded 267 CLog.i("Skip package %s because it was excluded", packageName); 268 return false; 269 } 270 if (mExcludeFilters.contains(className)) { 271 // Skip class because it was excluded 272 CLog.i("Skip class %s because it was excluded", className); 273 return false; 274 } 275 if (mExcludeFilters.contains(methodName)) { 276 // Skip method because it was excluded 277 CLog.i("Skip method %s in class %s because it was excluded", methodName, className); 278 return false; 279 } 280 return true; 281 } 282 } 283