1 /* 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 15 package com.android; 16 17 import static org.junit.Assert.assertFalse; 18 19 import android.support.test.filters.LargeTest; 20 import android.support.test.filters.MediumTest; 21 import android.support.test.filters.SmallTest; 22 import android.support.test.internal.runner.ClassPathScanner; 23 import android.support.test.internal.runner.ClassPathScanner.ChainedClassNameFilter; 24 import android.support.test.internal.runner.ClassPathScanner.ExternalClassNameFilter; 25 import android.testing.AndroidTestingRunner; 26 import android.text.TextUtils; 27 import android.util.Log; 28 29 import com.android.systemui.SysuiBaseFragmentTest; 30 import com.android.systemui.SysuiTestCase; 31 32 import org.junit.Test; 33 import org.junit.runner.RunWith; 34 import org.junit.internal.builders.AllDefaultPossibilitiesBuilder; 35 36 import java.io.IOException; 37 import java.lang.reflect.Method; 38 import java.lang.reflect.Modifier; 39 import java.util.Arrays; 40 import java.util.Collection; 41 import 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 55 public 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 testAllClassInheritance()74 public void testAllClassInheritance() throws Throwable { 75 boolean anyClassWrong = false; 76 for (String className : getClassNamesFromClassPath()) { 77 Class<?> cls = Class.forName(className, false, this.getClass().getClassLoader()); 78 if (!isTestClass(cls)) continue; 79 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 hasSize(Class<?> cls)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 getClassNamesFromClassPath()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 getClsStr()125 private String getClsStr() { 126 return TextUtils.join(",", Arrays.asList(BASE_CLS_WHITELIST) 127 .stream().map(cls -> cls.getSimpleName()).toArray()); 128 } 129 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 */ isTestClass(Class<?> loadedClass)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 hasJUnit3TestMethod(Class<?> loadedClass)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 isPublicTestMethod(Method m)191 private boolean isPublicTestMethod(Method m) { 192 return isTestMethod(m) && Modifier.isPublic(m.getModifiers()); 193 } 194 195 // copied from junit.framework.TestSuite isTestMethod(Method m)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 */ logDebug(String msg)205 private void logDebug(String msg) { 206 if (Log.isLoggable(TAG, Log.DEBUG)) { 207 Log.d(TAG, msg); 208 } 209 } 210 } 211