• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 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;
18 
19 import static org.hamcrest.Matchers.empty;
20 import static org.hamcrest.Matchers.is;
21 import static org.junit.Assert.assertThat;
22 
23 import android.testing.AndroidTestingRunner;
24 import android.text.TextUtils;
25 import android.util.Log;
26 
27 import androidx.test.filters.LargeTest;
28 import androidx.test.filters.MediumTest;
29 import androidx.test.filters.SmallTest;
30 import androidx.test.internal.runner.ClassPathScanner;
31 import androidx.test.internal.runner.ClassPathScanner.ChainedClassNameFilter;
32 import androidx.test.internal.runner.ClassPathScanner.ExternalClassNameFilter;
33 
34 import com.android.systemui.SysuiBaseFragmentTest;
35 import com.android.systemui.SysuiTestCase;
36 import com.android.systemui.car.CarSystemUiTest;
37 
38 import org.junit.Test;
39 import org.junit.runner.RunWith;
40 
41 import java.io.IOException;
42 import java.lang.reflect.Method;
43 import java.lang.reflect.Modifier;
44 import java.util.ArrayList;
45 import java.util.Arrays;
46 import java.util.Collection;
47 import java.util.Collections;
48 
49 /**
50  * This is named AAAPlusPlusVerifySysuiRequiredTestPropertiesTest for two reasons.
51  * a) Its so awesome it deserves an AAA++
52  * b) It should run first to draw attention to itself.
53  *
54  * For trues though: this test verifies that all the sysui tests extend the right classes.
55  * This matters because including tests with different context implementations in the same
56  * test suite causes errors, such as the incorrect settings provider being cached.
57  * For an example, see {@link com.android.systemui.DependencyTest}.
58  */
59 @CarSystemUiTest
60 @RunWith(AndroidTestingRunner.class)
61 @SmallTest
62 public class AAAPlusPlusVerifySysuiRequiredTestPropertiesTest extends SysuiTestCase {
63 
64     private static final String TAG = "AAA++VerifyTest";
65 
66     private static final Class[] BASE_CLS_TO_INCLUDE = {
67             SysuiTestCase.class,
68             SysuiBaseFragmentTest.class,
69     };
70 
71     private static final Class[] SUPPORTED_SIZES = {
72             SmallTest.class,
73             MediumTest.class,
74             LargeTest.class,
75             android.test.suitebuilder.annotation.SmallTest.class,
76             android.test.suitebuilder.annotation.MediumTest.class,
77             android.test.suitebuilder.annotation.LargeTest.class,
78     };
79 
80     @Test
testAllClassInheritance()81     public void testAllClassInheritance() throws Throwable {
82         ArrayList<String> fails = new ArrayList<>();
83         for (String className : getClassNamesFromClassPath()) {
84             Class<?> cls = Class.forName(className, false, this.getClass().getClassLoader());
85             if (!isTestClass(cls)) continue;
86 
87             boolean hasParent = false;
88             for (Class<?> parent : BASE_CLS_TO_INCLUDE) {
89                 if (parent.isAssignableFrom(cls)) {
90                     hasParent = true;
91                     break;
92                 }
93             }
94             boolean hasSize = hasSize(cls);
95             if (!hasSize) {
96                 fails.add(cls.getName() + " does not have size annotation, such as @SmallTest");
97             }
98             if (!hasParent) {
99                 fails.add(cls.getName() + " does not extend any of " + getClsStr());
100             }
101         }
102 
103         assertThat("All sysui test classes must have size and extend one of " + getClsStr(),
104                 fails, is(empty()));
105     }
106 
hasSize(Class<?> cls)107     private boolean hasSize(Class<?> cls) {
108         for (int i = 0; i < SUPPORTED_SIZES.length; i++) {
109             if (cls.getDeclaredAnnotation(SUPPORTED_SIZES[i]) != null) return true;
110         }
111         return false;
112     }
113 
getClassNamesFromClassPath()114     private Collection<String> getClassNamesFromClassPath() {
115         ClassPathScanner scanner = new ClassPathScanner(mContext.getPackageCodePath());
116 
117         ChainedClassNameFilter filter = new ChainedClassNameFilter();
118 
119         filter.add(new ExternalClassNameFilter());
120         filter.add(s -> s.startsWith("com.android.systemui")
121                 || s.startsWith("com.android.keyguard"));
122 
123         try {
124             return scanner.getClassPathEntries(filter);
125         } catch (IOException e) {
126             Log.e(TAG, "Failed to scan classes", e);
127         }
128         return Collections.emptyList();
129     }
130 
getClsStr()131     private String getClsStr() {
132         return TextUtils.join(",", Arrays.asList(BASE_CLS_TO_INCLUDE)
133                 .stream().map(cls -> cls.getSimpleName()).toArray());
134     }
135 
136     /**
137      * Determines if given class is a valid test class.
138      *
139      * @return <code>true</code> if loadedClass is a test
140      */
isTestClass(Class<?> loadedClass)141     private boolean isTestClass(Class<?> loadedClass) {
142         try {
143             if (Modifier.isAbstract(loadedClass.getModifiers())) {
144                 logDebug(String.format("Skipping abstract class %s: not a test",
145                         loadedClass.getName()));
146                 return false;
147             }
148             // TODO: try to find upstream junit calls to replace these checks
149             if (junit.framework.Test.class.isAssignableFrom(loadedClass)) {
150                 // ensure that if a TestCase, it has at least one test method otherwise
151                 // TestSuite will throw error
152                 if (junit.framework.TestCase.class.isAssignableFrom(loadedClass)) {
153                     return hasJUnit3TestMethod(loadedClass);
154                 }
155                 return true;
156             }
157             // TODO: look for a 'suite' method?
158             if (loadedClass.isAnnotationPresent(RunWith.class)) {
159                 return true;
160             }
161             for (Method testMethod : loadedClass.getMethods()) {
162                 if (testMethod.isAnnotationPresent(Test.class)) {
163                     return true;
164                 }
165             }
166             logDebug(String.format("Skipping class %s: not a test", loadedClass.getName()));
167             return false;
168         } catch (Exception e) {
169             // Defensively catch exceptions - Will throw runtime exception if it cannot load
170             // methods.
171             // For earlier versions of Android (Pre-ICS), Dalvik might try to initialize a class
172             // during getMethods(), fail to do so, hide the error and throw a NoSuchMethodException.
173             // Since the java.lang.Class.getMethods does not declare such an exception, resort to a
174             // generic catch all.
175             // For ICS+, Dalvik will throw a NoClassDefFoundException.
176             Log.w(TAG, String.format("%s in isTestClass for %s", e.toString(),
177                     loadedClass.getName()));
178             return false;
179         } catch (Error e) {
180             // defensively catch Errors too
181             Log.w(TAG, String.format("%s in isTestClass for %s", e.toString(),
182                     loadedClass.getName()));
183             return false;
184         }
185     }
186 
hasJUnit3TestMethod(Class<?> loadedClass)187     private boolean hasJUnit3TestMethod(Class<?> loadedClass) {
188         for (Method testMethod : loadedClass.getMethods()) {
189             if (isPublicTestMethod(testMethod)) {
190                 return true;
191             }
192         }
193         return false;
194     }
195 
196     // copied from junit.framework.TestSuite
isPublicTestMethod(Method m)197     private boolean isPublicTestMethod(Method m) {
198         return isTestMethod(m) && Modifier.isPublic(m.getModifiers());
199     }
200 
201     // copied from junit.framework.TestSuite
isTestMethod(Method m)202     private boolean isTestMethod(Method m) {
203         return m.getParameterTypes().length == 0 && m.getName().startsWith("test")
204                 && m.getReturnType().equals(Void.TYPE);
205     }
206 
207     /**
208      * Utility method for logging debug messages. Only actually logs a message if TAG is marked
209      * as loggable to limit log spam during normal use.
210      */
logDebug(String msg)211     private void logDebug(String msg) {
212         if (Log.isLoggable(TAG, Log.DEBUG)) {
213             Log.d(TAG, msg);
214         }
215     }
216 }
217