Merge "Add test to ensure all future fragments implements logging."
This commit is contained in:
committed by
Android (Google) Code Review
commit
fcbb6528ae
@@ -17,6 +17,7 @@ package com.android.settings;
|
|||||||
|
|
||||||
import android.app.Dialog;
|
import android.app.Dialog;
|
||||||
import android.app.Fragment;
|
import android.app.Fragment;
|
||||||
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
@@ -0,0 +1,130 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016 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.settings.core.codeinspection;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.JarURLConnection;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.net.URLConnection;
|
||||||
|
import java.net.URLDecoder;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Enumeration;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.jar.JarEntry;
|
||||||
|
import java.util.jar.JarFile;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scans and builds all classes in current classloader.
|
||||||
|
*/
|
||||||
|
public class ClassScanner {
|
||||||
|
|
||||||
|
private static final String CLASS_SUFFIX = ".class";
|
||||||
|
|
||||||
|
public List<Class<?>> getClassesForPackage(String packageName)
|
||||||
|
throws ClassNotFoundException {
|
||||||
|
final List<Class<?>> classes = new ArrayList<>();
|
||||||
|
|
||||||
|
try {
|
||||||
|
final Enumeration<URL> resources = Thread.currentThread().getContextClassLoader()
|
||||||
|
.getResources(packageName.replace('.', '/'));
|
||||||
|
if (!resources.hasMoreElements()) {
|
||||||
|
return classes;
|
||||||
|
}
|
||||||
|
URL url = resources.nextElement();
|
||||||
|
while (url != null) {
|
||||||
|
final URLConnection connection = url.openConnection();
|
||||||
|
|
||||||
|
if (connection instanceof JarURLConnection) {
|
||||||
|
loadClassFromJar((JarURLConnection) connection, packageName,
|
||||||
|
classes);
|
||||||
|
} else {
|
||||||
|
loadClassFromDirectory(new File(URLDecoder.decode(url.getPath(), "UTF-8")),
|
||||||
|
packageName, classes);
|
||||||
|
}
|
||||||
|
if (resources.hasMoreElements()) {
|
||||||
|
url = resources.nextElement();
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (final IOException e) {
|
||||||
|
throw new ClassNotFoundException("Error when parsing " + packageName, e);
|
||||||
|
}
|
||||||
|
return classes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadClassFromDirectory(File directory, String packageName, List<Class<?>> classes)
|
||||||
|
throws ClassNotFoundException {
|
||||||
|
if (directory.exists() && directory.isDirectory()) {
|
||||||
|
final String[] files = directory.list();
|
||||||
|
|
||||||
|
for (final String file : files) {
|
||||||
|
if (file.endsWith(CLASS_SUFFIX)) {
|
||||||
|
try {
|
||||||
|
classes.add(Class.forName(
|
||||||
|
packageName + '.' + file.substring(0, file.length() - 6),
|
||||||
|
false /* init */,
|
||||||
|
Thread.currentThread().getContextClassLoader()));
|
||||||
|
} catch (NoClassDefFoundError e) {
|
||||||
|
// do nothing. this class hasn't been found by the
|
||||||
|
// loader, and we don't care.
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
final File tmpDirectory = new File(directory, file);
|
||||||
|
if (tmpDirectory.isDirectory()) {
|
||||||
|
loadClassFromDirectory(tmpDirectory, packageName + "." + file, classes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadClassFromJar(JarURLConnection connection, String packageName,
|
||||||
|
List<Class<?>> classes) throws ClassNotFoundException, IOException {
|
||||||
|
final JarFile jarFile = connection.getJarFile();
|
||||||
|
final Enumeration<JarEntry> entries = jarFile.entries();
|
||||||
|
String name;
|
||||||
|
if (!entries.hasMoreElements()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
JarEntry jarEntry = entries.nextElement();
|
||||||
|
while (jarEntry != null) {
|
||||||
|
name = jarEntry.getName();
|
||||||
|
|
||||||
|
if (name.contains(CLASS_SUFFIX)) {
|
||||||
|
name = name.substring(0, name.length() - CLASS_SUFFIX.length()).replace('/', '.');
|
||||||
|
|
||||||
|
if (name.startsWith(packageName)) {
|
||||||
|
try {
|
||||||
|
classes.add(Class.forName(name,
|
||||||
|
false /* init */,
|
||||||
|
Thread.currentThread().getContextClassLoader()));
|
||||||
|
} catch (NoClassDefFoundError e) {
|
||||||
|
// do nothing. this class hasn't been found by the
|
||||||
|
// loader, and we don't care.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (entries.hasMoreElements()) {
|
||||||
|
jarEntry = entries.nextElement();
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016 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.settings.core.codeinspection;
|
||||||
|
|
||||||
|
import com.android.settings.SettingsRobolectricTestRunner;
|
||||||
|
import com.android.settings.TestConfig;
|
||||||
|
import com.android.settings.core.instrumentation.InstrumentableFragmentCodeInspector;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.robolectric.annotation.Config;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test suite that scans all class in app package, and perform different types of code inspection
|
||||||
|
* for conformance.
|
||||||
|
*/
|
||||||
|
@RunWith(SettingsRobolectricTestRunner.class)
|
||||||
|
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
|
||||||
|
public class CodeInspectionTest {
|
||||||
|
|
||||||
|
private List<Class<?>> mClasses;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() throws Exception {
|
||||||
|
mClasses = new ClassScanner().getClassesForPackage(CodeInspector.PACKAGE_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void runCodeInspections() {
|
||||||
|
new InstrumentableFragmentCodeInspector(mClasses).run();
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016 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.settings.core.codeinspection;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inspector takes a list of class objects and perform static code analysis in its {@link #run()}
|
||||||
|
* method.
|
||||||
|
*/
|
||||||
|
public abstract class CodeInspector {
|
||||||
|
|
||||||
|
public static final String PACKAGE_NAME = "com.android.settings";
|
||||||
|
|
||||||
|
protected final List<Class<?>> mClasses;
|
||||||
|
|
||||||
|
public CodeInspector(List<Class<?>> classes) {
|
||||||
|
mClasses = classes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Code inspection runner method.
|
||||||
|
*/
|
||||||
|
public abstract void run();
|
||||||
|
}
|
@@ -0,0 +1,108 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016 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.settings.core.instrumentation;
|
||||||
|
|
||||||
|
import android.app.Fragment;
|
||||||
|
import android.util.ArraySet;
|
||||||
|
|
||||||
|
import com.android.settings.ChooseLockPassword;
|
||||||
|
import com.android.settings.ChooseLockPattern;
|
||||||
|
import com.android.settings.CredentialCheckResultTracker;
|
||||||
|
import com.android.settings.CustomDialogPreference;
|
||||||
|
import com.android.settings.CustomEditTextPreference;
|
||||||
|
import com.android.settings.CustomListPreference;
|
||||||
|
import com.android.settings.RestrictedListPreference;
|
||||||
|
import com.android.settings.applications.AppOpsCategory;
|
||||||
|
import com.android.settings.core.codeinspection.CodeInspector;
|
||||||
|
import com.android.settings.core.lifecycle.ObservableDialogFragment;
|
||||||
|
import com.android.settings.deletionhelper.ActivationWarningFragment;
|
||||||
|
import com.android.settings.inputmethod.UserDictionaryLocalePicker;
|
||||||
|
|
||||||
|
import java.lang.reflect.Modifier;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static com.google.common.truth.Truth.assertWithMessage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link CodeInspector} that verifies all fragments implements Instrumentable.
|
||||||
|
*/
|
||||||
|
public class InstrumentableFragmentCodeInspector extends CodeInspector {
|
||||||
|
|
||||||
|
private static final String TEST_CLASS_SUFFIX = "Test";
|
||||||
|
|
||||||
|
private static final List<String> whitelist;
|
||||||
|
|
||||||
|
static {
|
||||||
|
whitelist = new ArrayList<>();
|
||||||
|
whitelist.add(
|
||||||
|
CustomEditTextPreference.CustomPreferenceDialogFragment.class.getName());
|
||||||
|
whitelist.add(
|
||||||
|
CustomListPreference.CustomListPreferenceDialogFragment.class.getName());
|
||||||
|
whitelist.add(
|
||||||
|
RestrictedListPreference.RestrictedListPreferenceDialogFragment.class.getName());
|
||||||
|
whitelist.add(ChooseLockPassword.SaveAndFinishWorker.class.getName());
|
||||||
|
whitelist.add(ChooseLockPattern.SaveAndFinishWorker.class.getName());
|
||||||
|
whitelist.add(ActivationWarningFragment.class.getName());
|
||||||
|
whitelist.add(ObservableDialogFragment.class.getName());
|
||||||
|
whitelist.add(CustomDialogPreference.CustomPreferenceDialogFragment.class.getName());
|
||||||
|
whitelist.add(AppOpsCategory.class.getName());
|
||||||
|
whitelist.add(UserDictionaryLocalePicker.class.getName());
|
||||||
|
whitelist.add(CredentialCheckResultTracker.class.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
public InstrumentableFragmentCodeInspector(List<Class<?>> classes) {
|
||||||
|
super(classes);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
final Set<String> broken = new ArraySet<>();
|
||||||
|
|
||||||
|
for (Class clazz : mClasses) {
|
||||||
|
// Skip abstract classes.
|
||||||
|
if (Modifier.isAbstract(clazz.getModifiers())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
final String packageName = clazz.getPackage().getName();
|
||||||
|
// Skip classes that are not in Settings.
|
||||||
|
if (!packageName.contains(PACKAGE_NAME + ".")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
final String className = clazz.getName();
|
||||||
|
// Skip classes from tests.
|
||||||
|
if (className.endsWith(TEST_CLASS_SUFFIX)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// If it's a fragment, it must also be instrumentable.
|
||||||
|
if (Fragment.class.isAssignableFrom(clazz)
|
||||||
|
&& !Instrumentable.class.isAssignableFrom(clazz)
|
||||||
|
&& !whitelist.contains(className)) {
|
||||||
|
broken.add(className);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final StringBuilder sb = new StringBuilder(
|
||||||
|
"All fragment should implement Instrumentable, but the following are not:\n");
|
||||||
|
for (String c : broken) {
|
||||||
|
sb.append(c).append("\n");
|
||||||
|
}
|
||||||
|
assertWithMessage(sb.toString())
|
||||||
|
.that(broken.isEmpty())
|
||||||
|
.isTrue();
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user