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