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