Home | History | Annotate | Download | only in android
      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
     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 
    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     }
    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      */
    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     }
    210 }
    211