Loading tests/robotests/src/com/android/settings/SettingsDialogFragmentTest.java +1 −0 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.settings; import android.app.Dialog; import android.app.Fragment; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; Loading tests/robotests/src/com/android/settings/core/codeinspection/ClassScanner.java 0 → 100644 +130 −0 Original line number Diff line number Diff line /* * 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; } } } } tests/robotests/src/com/android/settings/core/codeinspection/CodeInspectionTest.java 0 → 100644 +49 −0 Original line number Diff line number Diff line /* * 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(); } } tests/robotests/src/com/android/settings/core/codeinspection/CodeInspector.java 0 → 100644 +39 −0 Original line number Diff line number Diff line /* * 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(); } tests/robotests/src/com/android/settings/core/instrumentation/InstrumentableFragmentCodeInspector.java 0 → 100644 +108 −0 Original line number Diff line number Diff line /* * 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(); } } Loading
tests/robotests/src/com/android/settings/SettingsDialogFragmentTest.java +1 −0 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.settings; import android.app.Dialog; import android.app.Fragment; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; Loading
tests/robotests/src/com/android/settings/core/codeinspection/ClassScanner.java 0 → 100644 +130 −0 Original line number Diff line number Diff line /* * 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; } } } }
tests/robotests/src/com/android/settings/core/codeinspection/CodeInspectionTest.java 0 → 100644 +49 −0 Original line number Diff line number Diff line /* * 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(); } }
tests/robotests/src/com/android/settings/core/codeinspection/CodeInspector.java 0 → 100644 +39 −0 Original line number Diff line number Diff line /* * 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(); }
tests/robotests/src/com/android/settings/core/instrumentation/InstrumentableFragmentCodeInspector.java 0 → 100644 +108 −0 Original line number Diff line number Diff line /* * 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(); } }