blob: b97f55ce06d70ede7d032f5a4f62f0c856b6e889 [file] [log] [blame]
Jason Monkfba8faf2017-05-23 10:42:59 -04001/*
2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
5 * except in compliance with the License. You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software distributed under the
10 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
11 * KIND, either express or implied. See the License for the specific language governing
12 * permissions and limitations under the License.
13 */
14
15package com.android;
16
Adrian Roos86cedba2018-04-27 15:03:10 +020017import static org.hamcrest.Matchers.empty;
18import static org.hamcrest.Matchers.is;
Adrian Roos86cedba2018-04-27 15:03:10 +020019import static org.junit.Assert.assertThat;
Jason Monkfba8faf2017-05-23 10:42:59 -040020
jovanakf40566e2018-08-20 14:23:04 -070021import android.content.pm.PackageManager;
Jason Monkfba8faf2017-05-23 10:42:59 -040022import android.testing.AndroidTestingRunner;
23import android.text.TextUtils;
24import android.util.Log;
25
Brett Chabot84151d92019-02-27 15:37:59 -080026import androidx.test.filters.LargeTest;
27import androidx.test.filters.MediumTest;
28import androidx.test.filters.SmallTest;
29import androidx.test.internal.runner.ClassPathScanner;
30import androidx.test.internal.runner.ClassPathScanner.ChainedClassNameFilter;
31import androidx.test.internal.runner.ClassPathScanner.ExternalClassNameFilter;
32
Jason Monkfba8faf2017-05-23 10:42:59 -040033import com.android.systemui.SysuiBaseFragmentTest;
34import com.android.systemui.SysuiTestCase;
35
36import org.junit.Test;
37import org.junit.runner.RunWith;
38
39import java.io.IOException;
Brett Chabot85544ba2017-06-26 15:58:49 -070040import java.lang.reflect.Method;
41import java.lang.reflect.Modifier;
Adrian Roos86cedba2018-04-27 15:03:10 +020042import java.util.ArrayList;
Jason Monkfba8faf2017-05-23 10:42:59 -040043import java.util.Arrays;
44import java.util.Collection;
45import java.util.Collections;
46
47/**
48 * This is named AAAPlusPlusVerifySysuiRequiredTestPropertiesTest for two reasons.
49 * a) Its so awesome it deserves an AAA++
50 * b) It should run first to draw attention to itself.
51 *
52 * For trues though: this test verifies that all the sysui tests extend the right classes.
53 * This matters because including tests with different context implementations in the same
54 * test suite causes errors, such as the incorrect settings provider being cached.
55 * For an example, see {@link com.android.systemui.DependencyTest}.
56 */
57@RunWith(AndroidTestingRunner.class)
58@SmallTest
59public class AAAPlusPlusVerifySysuiRequiredTestPropertiesTest extends SysuiTestCase {
60
61 private static final String TAG = "AAA++VerifyTest";
62
63 private static final Class[] BASE_CLS_WHITELIST = {
64 SysuiTestCase.class,
65 SysuiBaseFragmentTest.class,
66 };
67
68 private static final Class[] SUPPORTED_SIZES = {
69 SmallTest.class,
70 MediumTest.class,
71 LargeTest.class,
72 android.test.suitebuilder.annotation.SmallTest.class,
73 android.test.suitebuilder.annotation.MediumTest.class,
74 android.test.suitebuilder.annotation.LargeTest.class,
75 };
76
77 @Test
Brett Chabot85544ba2017-06-26 15:58:49 -070078 public void testAllClassInheritance() throws Throwable {
Adrian Roos86cedba2018-04-27 15:03:10 +020079 ArrayList<String> fails = new ArrayList<>();
Jason Monkfba8faf2017-05-23 10:42:59 -040080 for (String className : getClassNamesFromClassPath()) {
Adrian Roos86cedba2018-04-27 15:03:10 +020081 Class<?> cls = Class.forName(className, false, this.getClass().getClassLoader());
Brett Chabot85544ba2017-06-26 15:58:49 -070082 if (!isTestClass(cls)) continue;
Jason Monkfba8faf2017-05-23 10:42:59 -040083
84 boolean hasParent = false;
85 for (Class<?> parent : BASE_CLS_WHITELIST) {
86 if (parent.isAssignableFrom(cls)) {
87 hasParent = true;
88 break;
89 }
90 }
91 boolean hasSize = hasSize(cls);
92 if (!hasSize) {
Adrian Roos86cedba2018-04-27 15:03:10 +020093 fails.add(cls.getName() + " does not have size annotation, such as @SmallTest");
Jason Monkfba8faf2017-05-23 10:42:59 -040094 }
95 if (!hasParent) {
Adrian Roos86cedba2018-04-27 15:03:10 +020096 fails.add(cls.getName() + " does not extend any of " + getClsStr());
Jason Monkfba8faf2017-05-23 10:42:59 -040097 }
98 }
99
Adrian Roos86cedba2018-04-27 15:03:10 +0200100 assertThat("All sysui test classes must have size and extend one of " + getClsStr(),
101 fails, is(empty()));
Jason Monkfba8faf2017-05-23 10:42:59 -0400102 }
103
104 private boolean hasSize(Class<?> cls) {
105 for (int i = 0; i < SUPPORTED_SIZES.length; i++) {
106 if (cls.getDeclaredAnnotation(SUPPORTED_SIZES[i]) != null) return true;
107 }
108 return false;
109 }
110
111 private Collection<String> getClassNamesFromClassPath() {
112 ClassPathScanner scanner = new ClassPathScanner(mContext.getPackageCodePath());
113
114 ChainedClassNameFilter filter = new ChainedClassNameFilter();
115
116 filter.add(new ExternalClassNameFilter());
117 filter.add(s -> s.startsWith("com.android.systemui")
118 || s.startsWith("com.android.keyguard"));
jovanakf40566e2018-08-20 14:23:04 -0700119
120
121 if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
122 // If it's not automotive target, exclude automotive classes from the test.
123 excludeAutomotiveClasses(filter);
124 }
125
Jason Monkfba8faf2017-05-23 10:42:59 -0400126 try {
127 return scanner.getClassPathEntries(filter);
128 } catch (IOException e) {
129 Log.e(TAG, "Failed to scan classes", e);
130 }
131 return Collections.emptyList();
132 }
133
jovanakf40566e2018-08-20 14:23:04 -0700134 private void excludeAutomotiveClasses(ChainedClassNameFilter filter) {
135 // Modifies the passed in filter.
136 filter.add(s -> !s.startsWith("com.android.systemui.statusbar.car."));
137 filter.add(s -> !s.startsWith("com.android.systemui.qs.car."));
138 filter.add(s -> !s.startsWith("com.android.systemui.car."));
139 }
140
Jason Monkfba8faf2017-05-23 10:42:59 -0400141 private String getClsStr() {
142 return TextUtils.join(",", Arrays.asList(BASE_CLS_WHITELIST)
143 .stream().map(cls -> cls.getSimpleName()).toArray());
144 }
Brett Chabot85544ba2017-06-26 15:58:49 -0700145
146 /**
147 * Determines if given class is a valid test class.
148 *
149 * @param loadedClass
150 * @return <code>true</code> if loadedClass is a test
151 */
152 private boolean isTestClass(Class<?> loadedClass) {
153 try {
154 if (Modifier.isAbstract(loadedClass.getModifiers())) {
155 logDebug(String.format("Skipping abstract class %s: not a test",
156 loadedClass.getName()));
157 return false;
158 }
159 // TODO: try to find upstream junit calls to replace these checks
160 if (junit.framework.Test.class.isAssignableFrom(loadedClass)) {
161 // ensure that if a TestCase, it has at least one test method otherwise
162 // TestSuite will throw error
163 if (junit.framework.TestCase.class.isAssignableFrom(loadedClass)) {
164 return hasJUnit3TestMethod(loadedClass);
165 }
166 return true;
167 }
168 // TODO: look for a 'suite' method?
169 if (loadedClass.isAnnotationPresent(org.junit.runner.RunWith.class)) {
170 return true;
171 }
172 for (Method testMethod : loadedClass.getMethods()) {
173 if (testMethod.isAnnotationPresent(org.junit.Test.class)) {
174 return true;
175 }
176 }
177 logDebug(String.format("Skipping class %s: not a test", loadedClass.getName()));
178 return false;
179 } catch (Exception e) {
180 // Defensively catch exceptions - Will throw runtime exception if it cannot load methods.
181 // For earlier versions of Android (Pre-ICS), Dalvik might try to initialize a class
182 // during getMethods(), fail to do so, hide the error and throw a NoSuchMethodException.
183 // Since the java.lang.Class.getMethods does not declare such an exception, resort to a
184 // generic catch all.
185 // For ICS+, Dalvik will throw a NoClassDefFoundException.
186 Log.w(TAG, String.format("%s in isTestClass for %s", e.toString(),
187 loadedClass.getName()));
188 return false;
189 } catch (Error e) {
190 // defensively catch Errors too
191 Log.w(TAG, String.format("%s in isTestClass for %s", e.toString(),
192 loadedClass.getName()));
193 return false;
194 }
195 }
196
197 private boolean hasJUnit3TestMethod(Class<?> loadedClass) {
198 for (Method testMethod : loadedClass.getMethods()) {
199 if (isPublicTestMethod(testMethod)) {
200 return true;
201 }
202 }
203 return false;
204 }
205
206 // copied from junit.framework.TestSuite
207 private boolean isPublicTestMethod(Method m) {
208 return isTestMethod(m) && Modifier.isPublic(m.getModifiers());
209 }
210
211 // copied from junit.framework.TestSuite
212 private boolean isTestMethod(Method m) {
213 return m.getParameterTypes().length == 0 && m.getName().startsWith("test")
214 && m.getReturnType().equals(Void.TYPE);
215 }
216
217 /**
218 * Utility method for logging debug messages. Only actually logs a message if TAG is marked
219 * as loggable to limit log spam during normal use.
220 */
221 private void logDebug(String msg) {
222 if (Log.isLoggable(TAG, Log.DEBUG)) {
223 Log.d(TAG, msg);
224 }
225 }
Jason Monkfba8faf2017-05-23 10:42:59 -0400226}