Files
frameworks_base/packages/CarSystemUI/tests/src/com/android/AAAPlusPlusVerifySysuiRequiredTestPropertiesTest.java
Heemin Seog d0cfab0964 Setup on device testing for sys ui
This borrows on the testing infrastructure set up by the phone sys ui
team and provides an examples via test for CarNavigationBarController.

Bug: 143227120
Test: atest CarNavigationBarControllerTest
Change-Id: I10fd4e2e51f2faf75ade5b40e47441a9e948f075
2019-10-29 10:51:06 -07:00

215 lines
7.9 KiB
Java

/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
import android.testing.AndroidTestingRunner;
import android.text.TextUtils;
import android.util.Log;
import androidx.test.filters.LargeTest;
import androidx.test.filters.MediumTest;
import androidx.test.filters.SmallTest;
import androidx.test.internal.runner.ClassPathScanner;
import androidx.test.internal.runner.ClassPathScanner.ChainedClassNameFilter;
import androidx.test.internal.runner.ClassPathScanner.ExternalClassNameFilter;
import com.android.systemui.SysuiBaseFragmentTest;
import com.android.systemui.SysuiTestCase;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.io.IOException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
/**
* This is named AAAPlusPlusVerifySysuiRequiredTestPropertiesTest for two reasons.
* a) Its so awesome it deserves an AAA++
* b) It should run first to draw attention to itself.
*
* For trues though: this test verifies that all the sysui tests extend the right classes.
* This matters because including tests with different context implementations in the same
* test suite causes errors, such as the incorrect settings provider being cached.
* For an example, see {@link com.android.systemui.DependencyTest}.
*/
@RunWith(AndroidTestingRunner.class)
@SmallTest
public class AAAPlusPlusVerifySysuiRequiredTestPropertiesTest extends SysuiTestCase {
private static final String TAG = "AAA++VerifyTest";
private static final Class[] BASE_CLS_WHITELIST = {
SysuiTestCase.class,
SysuiBaseFragmentTest.class,
};
private static final Class[] SUPPORTED_SIZES = {
SmallTest.class,
MediumTest.class,
LargeTest.class,
android.test.suitebuilder.annotation.SmallTest.class,
android.test.suitebuilder.annotation.MediumTest.class,
android.test.suitebuilder.annotation.LargeTest.class,
};
@Test
public void testAllClassInheritance() throws Throwable {
ArrayList<String> fails = new ArrayList<>();
for (String className : getClassNamesFromClassPath()) {
Class<?> cls = Class.forName(className, false, this.getClass().getClassLoader());
if (!isTestClass(cls)) continue;
boolean hasParent = false;
for (Class<?> parent : BASE_CLS_WHITELIST) {
if (parent.isAssignableFrom(cls)) {
hasParent = true;
break;
}
}
boolean hasSize = hasSize(cls);
if (!hasSize) {
fails.add(cls.getName() + " does not have size annotation, such as @SmallTest");
}
if (!hasParent) {
fails.add(cls.getName() + " does not extend any of " + getClsStr());
}
}
assertThat("All sysui test classes must have size and extend one of " + getClsStr(),
fails, is(empty()));
}
private boolean hasSize(Class<?> cls) {
for (int i = 0; i < SUPPORTED_SIZES.length; i++) {
if (cls.getDeclaredAnnotation(SUPPORTED_SIZES[i]) != null) return true;
}
return false;
}
private Collection<String> getClassNamesFromClassPath() {
ClassPathScanner scanner = new ClassPathScanner(mContext.getPackageCodePath());
ChainedClassNameFilter filter = new ChainedClassNameFilter();
filter.add(new ExternalClassNameFilter());
filter.add(s -> s.startsWith("com.android.systemui")
|| s.startsWith("com.android.keyguard"));
try {
return scanner.getClassPathEntries(filter);
} catch (IOException e) {
Log.e(TAG, "Failed to scan classes", e);
}
return Collections.emptyList();
}
private String getClsStr() {
return TextUtils.join(",", Arrays.asList(BASE_CLS_WHITELIST)
.stream().map(cls -> cls.getSimpleName()).toArray());
}
/**
* Determines if given class is a valid test class.
*
* @return <code>true</code> if loadedClass is a test
*/
private boolean isTestClass(Class<?> loadedClass) {
try {
if (Modifier.isAbstract(loadedClass.getModifiers())) {
logDebug(String.format("Skipping abstract class %s: not a test",
loadedClass.getName()));
return false;
}
// TODO: try to find upstream junit calls to replace these checks
if (junit.framework.Test.class.isAssignableFrom(loadedClass)) {
// ensure that if a TestCase, it has at least one test method otherwise
// TestSuite will throw error
if (junit.framework.TestCase.class.isAssignableFrom(loadedClass)) {
return hasJUnit3TestMethod(loadedClass);
}
return true;
}
// TODO: look for a 'suite' method?
if (loadedClass.isAnnotationPresent(RunWith.class)) {
return true;
}
for (Method testMethod : loadedClass.getMethods()) {
if (testMethod.isAnnotationPresent(Test.class)) {
return true;
}
}
logDebug(String.format("Skipping class %s: not a test", loadedClass.getName()));
return false;
} catch (Exception e) {
// Defensively catch exceptions - Will throw runtime exception if it cannot load
// methods.
// For earlier versions of Android (Pre-ICS), Dalvik might try to initialize a class
// during getMethods(), fail to do so, hide the error and throw a NoSuchMethodException.
// Since the java.lang.Class.getMethods does not declare such an exception, resort to a
// generic catch all.
// For ICS+, Dalvik will throw a NoClassDefFoundException.
Log.w(TAG, String.format("%s in isTestClass for %s", e.toString(),
loadedClass.getName()));
return false;
} catch (Error e) {
// defensively catch Errors too
Log.w(TAG, String.format("%s in isTestClass for %s", e.toString(),
loadedClass.getName()));
return false;
}
}
private boolean hasJUnit3TestMethod(Class<?> loadedClass) {
for (Method testMethod : loadedClass.getMethods()) {
if (isPublicTestMethod(testMethod)) {
return true;
}
}
return false;
}
// copied from junit.framework.TestSuite
private boolean isPublicTestMethod(Method m) {
return isTestMethod(m) && Modifier.isPublic(m.getModifiers());
}
// copied from junit.framework.TestSuite
private boolean isTestMethod(Method m) {
return m.getParameterTypes().length == 0 && m.getName().startsWith("test")
&& m.getReturnType().equals(Void.TYPE);
}
/**
* Utility method for logging debug messages. Only actually logs a message if TAG is marked
* as loggable to limit log spam during normal use.
*/
private void logDebug(String msg) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, msg);
}
}
}