diff --git a/tests/Android.mk b/tests/Android.mk index e8a7383..525b39e 100644 --- a/tests/Android.mk +++ b/tests/Android.mk @@ -19,7 +19,8 @@ include $(CLEAR_VARS) LOCAL_MODULE_TAGS := tests LOCAL_STATIC_JAVA_LIBRARIES := \ - org.cyanogenmod.platform.sdk + org.cyanogenmod.platform.sdk \ + android-support-test LOCAL_SRC_FILES := $(call all-subdir-java-files, src/) diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml index 8eeb269..5016a35 100644 --- a/tests/AndroidManifest.xml +++ b/tests/AndroidManifest.xml @@ -22,7 +22,8 @@ - + @@ -79,6 +80,6 @@ diff --git a/tests/README.md b/tests/README.md index a6e3b0c..9d32357 100644 --- a/tests/README.md +++ b/tests/README.md @@ -4,4 +4,4 @@ tests which can be ran utilizing the InstrumentationTestRunner from android. To run the tests (on a live device): - ```adb shell am instrument -w org.cyanogenmod.tests/android.test.InstrumentationTestRunner``` + ```adb shell am instrument -w org.cyanogenmod.tests/android.support.test.runner.AndroidJUnitRunner``` diff --git a/tests/src/org/cyanogenmod/tests/CyanogenModTestApplication.java b/tests/src/org/cyanogenmod/tests/CyanogenModTestApplication.java new file mode 100644 index 0000000..a738908 --- /dev/null +++ b/tests/src/org/cyanogenmod/tests/CyanogenModTestApplication.java @@ -0,0 +1,37 @@ +/** + * Copyright (c) 2016, The CyanogenMod 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 org.cyanogenmod.tests; + +import android.app.Application; +import android.content.Context; + +/** + * Created by adnan on 2/4/16. + */ +public class CyanogenModTestApplication extends Application { + private static Context sApplicationContext; + + @Override + public void onCreate() { + super.onCreate(); + sApplicationContext = getApplicationContext(); + } + + public static Context getStaticApplicationContext() { + return sApplicationContext; + } +} diff --git a/tests/src/org/cyanogenmod/tests/versioning/unit/BinderTransactionTest.java b/tests/src/org/cyanogenmod/tests/versioning/unit/BinderTransactionTest.java new file mode 100644 index 0000000..b83b727 --- /dev/null +++ b/tests/src/org/cyanogenmod/tests/versioning/unit/BinderTransactionTest.java @@ -0,0 +1,165 @@ +/** + * Copyright (c) 2016, The CyanogenMod 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 org.cyanogenmod.tests.versioning.unit; + +import android.content.Context; +import android.os.Binder; +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.LargeTest; +import android.test.suitebuilder.annotation.SmallTest; +import org.cyanogenmod.tests.CyanogenModTestApplication; +import org.cyanogenmod.tests.versioning.unit.apiv2.ApiV2PriorReleaseInterfaces; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import android.support.test.runner.AndroidJUnit4; +import android.support.test.runner.AndroidJUnitRunner; + +import java.io.IOException; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +/** + * These tests validate the enumerated binder transaction call ids per each + * release api level against the current framework. + * + * This is to validate that no interface contracts are broken in terms of binder + * call method mapping between releases. + * + * After too much searching on the internet, I found that no one was bored enough to + * spend time on this awesomely boring concept. But I am. + * + * Also this is a fun endeavour into parameterized unit testing, and by fun, I mean + * horrible and full of drinking. + * + * If you need to blame anyone for this concept, look no further than danesh@cyngn.com + */ +@RunWith(Parameterized.class) +@LargeTest +public class BinderTransactionTest extends AndroidTestCase { + private static final String STUB_SUFFIX = "$Stub"; + private static final String CYANOGENMOD_NAMESPACE = "cyanogenmod"; + private static final String TRANSACTION_PREFIX = "TRANSACTION_"; + + private static final int NOT_FROM_PRIOR_RELEASE = -1; + + private String mField; + private int mExpectedValue; + private int mActualValue; + private static Context sContext; + + private static ArrayList mKnownSdkClasses; + private static Map mApiMethodsAndValues = new HashMap(); + + @Before + public void setUp() throws Exception { + super.setUp(); + } + + @Test + public void testClassLoaderGivesSDKClasses() { + /** + * Verify we can retrieve our sdk classes from this package + */ + assertNotNull(mKnownSdkClasses); + assertTrue(mKnownSdkClasses.size() > 0); + } + + private static void doSetup() { + mKnownSdkClasses = MagicalDexHelper.getLoadedClasses( + CyanogenModTestApplication.getStaticApplicationContext(), CYANOGENMOD_NAMESPACE); + sContext = CyanogenModTestApplication.getStaticApplicationContext(); + mApiMethodsAndValues.putAll(ApiV2PriorReleaseInterfaces.getInterfaces()); + } + + @Parameterized.Parameters + public static Collection data() { + doSetup(); + //Ughhh, lets pretend this never happened + ArrayList targetFields = new ArrayList(); + ArrayList actualValues = new ArrayList(); + + for (String sClazz : mKnownSdkClasses) { + if (sClazz.endsWith(STUB_SUFFIX)) { + try { + Class clazz = MagicalDexHelper.loadClassForNameSpace(CyanogenModTestApplication + .getStaticApplicationContext(), sClazz); + Field[] fields = clazz.getDeclaredFields(); + + for (Field field : fields) { + if (field.getName().startsWith(TRANSACTION_PREFIX)) { + field.setAccessible(true); + targetFields.add(field.getName() + .substring(TRANSACTION_PREFIX.length())); + try { + actualValues.add(field.getInt(clazz)); + } catch (IllegalAccessException e) { + throw new AssertionError("Unable to access " + field.getName()); + } + } + } + } catch (IOException e) { + throw new AssertionError("Unable to load class " + sClazz); + } + } + } + Object[][] values = new Object[targetFields.size()][3]; + + for (int i = 0; i < targetFields.size(); i++) { + String targetField = targetFields.get(i); + values[i][0] = targetField; + values[i][1] = lookupValueForField(targetField); + values[i][2] = actualValues.get(i); + } + return Arrays.asList(values); + } + + //Look up the target fields value from a prior release + private static Object lookupValueForField(String fieldName) { + if (!mApiMethodsAndValues.containsKey(fieldName)) { + return NOT_FROM_PRIOR_RELEASE; + } + return mApiMethodsAndValues.get(fieldName); + } + + public BinderTransactionTest(String targetField, Integer expectedValue, Integer actualValue) { + mField = targetField; + mExpectedValue = expectedValue; + mActualValue = actualValue; + } + + @Test + public void testBinderTransactionValidation() { + System.out.print("Testing: " + mField); + if (mExpectedValue == NOT_FROM_PRIOR_RELEASE) { + //This is a new interface, no need to test against + return; + } + try { + assertEquals(mExpectedValue, mActualValue); + } catch (AssertionError e) { + throw new AssertionError("For the field " + mField + " expected value " + + mExpectedValue + " but got " + mActualValue); + } + } +} diff --git a/tests/src/org/cyanogenmod/tests/versioning/unit/ClassPathTest.java b/tests/src/org/cyanogenmod/tests/versioning/unit/ClassPathTest.java index e1ae1c3..481ae7b 100644 --- a/tests/src/org/cyanogenmod/tests/versioning/unit/ClassPathTest.java +++ b/tests/src/org/cyanogenmod/tests/versioning/unit/ClassPathTest.java @@ -39,7 +39,7 @@ public class ClassPathTest extends AndroidTestCase { @Override protected void setUp() throws Exception { super.setUp(); - mKnownSdkClasses = getLoadedClasses(); + mKnownSdkClasses = MagicalDexHelper.getLoadedClasses(mContext, CYANOGENMOD_NAMESPACE); } @SmallTest @@ -86,24 +86,6 @@ public class ClassPathTest extends AndroidTestCase { } } - private ArrayList getLoadedClasses() { - ArrayList listOfClasses = new ArrayList(); - try { - DexFile dexFile = new DexFile(new File(mContext.getPackageCodePath())); - Enumeration enumeration = dexFile.entries(); - - while (enumeration.hasMoreElements()){ - String className = enumeration.nextElement(); - if (className.startsWith(CYANOGENMOD_NAMESPACE)) { - listOfClasses.add(className); - } - } - } catch (IOException e) { - e.printStackTrace(); - } - return listOfClasses; - } - private void processAndCompare(String name) throws ClassPathException { if (mKnownSdkClasses.contains(name)) { throw new ClassPathException(name); diff --git a/tests/src/org/cyanogenmod/tests/versioning/unit/MagicalDexHelper.java b/tests/src/org/cyanogenmod/tests/versioning/unit/MagicalDexHelper.java new file mode 100644 index 0000000..a7f2ccc --- /dev/null +++ b/tests/src/org/cyanogenmod/tests/versioning/unit/MagicalDexHelper.java @@ -0,0 +1,55 @@ +/** + * Copyright (c) 2016, The CyanogenMod 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 org.cyanogenmod.tests.versioning.unit; + +import android.content.Context; +import dalvik.system.DexFile; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Enumeration; + +/** + * Created by adnan on 2/3/16. + */ +public class MagicalDexHelper { + + public static ArrayList getLoadedClasses(Context context, String targetNameSpace) { + ArrayList listOfClasses = new ArrayList(); + try { + DexFile dexFile = new DexFile(new File(context.getPackageCodePath())); + Enumeration enumeration = dexFile.entries(); + + while (enumeration.hasMoreElements()){ + String className = enumeration.nextElement(); + if (className.startsWith(targetNameSpace)) { + listOfClasses.add(className); + } + } + } catch (IOException e) { + e.printStackTrace(); + } + return listOfClasses; + } + + public static Class loadClassForNameSpace(Context context, + String classToLoad) throws IOException { + DexFile dexFile = new DexFile(new File(context.getPackageCodePath())); + return dexFile.loadClass(classToLoad, context.getClassLoader()); + } +} diff --git a/tests/src/org/cyanogenmod/tests/versioning/unit/apiv2/ApiV2PriorReleaseInterfaces.java b/tests/src/org/cyanogenmod/tests/versioning/unit/apiv2/ApiV2PriorReleaseInterfaces.java new file mode 100644 index 0000000..26a0ce9 --- /dev/null +++ b/tests/src/org/cyanogenmod/tests/versioning/unit/apiv2/ApiV2PriorReleaseInterfaces.java @@ -0,0 +1,118 @@ +/** + * Copyright (c) 2016, The CyanogenMod 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 org.cyanogenmod.tests.versioning.unit.apiv2; +import java.util.HashMap; +import java.util.Map; + +/** + * Created by adnan on 2/4/16. + */ +public class ApiV2PriorReleaseInterfaces { + private static Map mApiMethodsAndValues = new HashMap(); + + //Profiles Aidl (IProfileManager) + static { + // APRICOT + BOYSENBERRY + CANTALOUPE + mApiMethodsAndValues.put("setActiveProfile", 1); + mApiMethodsAndValues.put("etActiveProfileByName", 2); + mApiMethodsAndValues.put("getActiveProfile", 3); + mApiMethodsAndValues.put("addProfile", 4); + mApiMethodsAndValues.put("removeProfile", 5); + mApiMethodsAndValues.put("updateProfile", 6); + mApiMethodsAndValues.put("getProfile", 7); + mApiMethodsAndValues.put("getProfileByName", 8); + mApiMethodsAndValues.put("getProfiles", 9); + mApiMethodsAndValues.put("profileExists", 10); + mApiMethodsAndValues.put("profileExistsByName", 11); + mApiMethodsAndValues.put("notificationGroupExistsByName", 12); + mApiMethodsAndValues.put("getNotificationGroups", 13); + mApiMethodsAndValues.put("addNotificationGroup", 14); + mApiMethodsAndValues.put("removeNotificationGroup", 15); + mApiMethodsAndValues.put("updateNotificationGroup", 16); + mApiMethodsAndValues.put("getNotificationGroupForPackage", 17); + mApiMethodsAndValues.put("getNotificationGroup", 18); + mApiMethodsAndValues.put("resetAll", 19); + + //FUTURE RELEASE + } + + //PartnerInterface Aidl (IPartnerInterface) + static { + // APRICOT + BOYSENBERRY + CANTALOUPE + mApiMethodsAndValues.put("setAirplaneModeEnabled_0", 1); + mApiMethodsAndValues.put("setMobileDataEnabled_1", 2); + mApiMethodsAndValues.put("setZenMode", 3); + mApiMethodsAndValues.put("shutdown", 4); + mApiMethodsAndValues.put("reboot", 5); + mApiMethodsAndValues.put("getCurrentHotwordPackageName", 6); + + //FUTURE RELEASE + } + + //CMHardwareManager Aidl (ICMHardwareService) + static { + // APRICOT + BOYSENBERRY + CANTALOUPE + mApiMethodsAndValues.put("getSupportedFeatures_0", 1); + mApiMethodsAndValues.put("get_1", 2); + mApiMethodsAndValues.put("set", 3); + mApiMethodsAndValues.put("getDisplayColorCalibration", 4); + mApiMethodsAndValues.put("setDisplayColorCalibration", 5); + mApiMethodsAndValues.put("getNumGammaControls", 6); + mApiMethodsAndValues.put("getDisplayGammaCalibration", 7); + mApiMethodsAndValues.put("setDisplayGammaCalibration", 8); + mApiMethodsAndValues.put("getVibratorIntensity", 9); + mApiMethodsAndValues.put("setVibratorIntensity", 10); + mApiMethodsAndValues.put("getLtoSource", 11); + mApiMethodsAndValues.put("getLtoDestination", 12); + mApiMethodsAndValues.put("getLtoDownloadInterval", 13); + mApiMethodsAndValues.put("getSerialNumber", 14); + mApiMethodsAndValues.put("requireAdaptiveBacklightForSunlightEnhancement", 15); + mApiMethodsAndValues.put("getDisplayModes", 16); + mApiMethodsAndValues.put("getCurrentDisplayMode", 17); + mApiMethodsAndValues.put("getDefaultDisplayMode", 18); + mApiMethodsAndValues.put("setDisplayMode", 19); + mApiMethodsAndValues.put("writePersistentBytes", 20); + mApiMethodsAndValues.put("readPersistentBytes", 21); + mApiMethodsAndValues.put("getThermalState", 22); + mApiMethodsAndValues.put("registerThermalListener", 23); + mApiMethodsAndValues.put("unRegisterThermalListener", 24); + + //FUTURE RELEASE + } + + //CMStatusBarManager Aidl (ICMStatusBarManager) + static { + // APRICOT + BOYSENBERRY + CANTALOUPE + mApiMethodsAndValues.put("createCustomTileWithTag", 1); + mApiMethodsAndValues.put("removeCustomTileWithTag", 2); + mApiMethodsAndValues.put("registerListener", 3); + mApiMethodsAndValues.put("unregisterListener", 4); + mApiMethodsAndValues.put("removeCustomTileFromListener", 5); + + //FUTURE RELEASE + } + + //AppSuggestManager Aidl (IAppSuggestManager) + static { + mApiMethodsAndValues.put("handles_0", 1); + mApiMethodsAndValues.put("getSuggestions_1", 2); + } + + public static Map getInterfaces() { + return mApiMethodsAndValues; + } +}