1 /* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.signature.cts; 18 19 import android.content.res.Resources; 20 import android.signature.R; 21 import android.signature.cts.JDiffClassDescription.JDiffConstructor; 22 import android.signature.cts.JDiffClassDescription.JDiffField; 23 import android.signature.cts.JDiffClassDescription.JDiffMethod; 24 import android.test.AndroidTestCase; 25 import android.util.Log; 26 27 import org.xmlpull.v1.XmlPullParser; 28 import org.xmlpull.v1.XmlPullParserException; 29 30 import java.io.IOException; 31 import java.lang.reflect.Field; 32 import java.lang.reflect.Modifier; 33 import java.util.ArrayList; 34 import java.util.Arrays; 35 import java.util.HashSet; 36 37 /** 38 * Performs the signature check via a JUnit test. 39 */ 40 public class SignatureTest extends AndroidTestCase { 41 42 private static final String TAG = SignatureTest.class.getSimpleName(); 43 44 private static final String TAG_ROOT = "api"; 45 private static final String TAG_PACKAGE = "package"; 46 private static final String TAG_CLASS = "class"; 47 private static final String TAG_INTERFACE = "interface"; 48 private static final String TAG_IMPLEMENTS = "implements"; 49 private static final String TAG_CONSTRUCTOR = "constructor"; 50 private static final String TAG_METHOD = "method"; 51 private static final String TAG_PARAM = "parameter"; 52 private static final String TAG_EXCEPTION = "exception"; 53 private static final String TAG_FIELD = "field"; 54 55 private static final String MODIFIER_ABSTRACT = "abstract"; 56 private static final String MODIFIER_FINAL = "final"; 57 private static final String MODIFIER_NATIVE = "native"; 58 private static final String MODIFIER_PRIVATE = "private"; 59 private static final String MODIFIER_PROTECTED = "protected"; 60 private static final String MODIFIER_PUBLIC = "public"; 61 private static final String MODIFIER_STATIC = "static"; 62 private static final String MODIFIER_SYNCHRONIZED = "synchronized"; 63 private static final String MODIFIER_TRANSIENT = "transient"; 64 private static final String MODIFIER_VOLATILE = "volatile"; 65 private static final String MODIFIER_VISIBILITY = "visibility"; 66 67 private static final String ATTRIBUTE_NAME = "name"; 68 private static final String ATTRIBUTE_EXTENDS = "extends"; 69 private static final String ATTRIBUTE_TYPE = "type"; 70 private static final String ATTRIBUTE_RETURN = "return"; 71 72 private HashSet<String> mKeyTagSet; 73 private TestResultObserver mResultObserver; 74 75 private class TestResultObserver implements ResultObserver { 76 boolean mDidFail = false; 77 StringBuilder mErrorString = new StringBuilder(); 78 79 @Override notifyFailure(FailureType type, String name, String errorMessage)80 public void notifyFailure(FailureType type, String name, String errorMessage) { 81 mDidFail = true; 82 mErrorString.append("\n"); 83 mErrorString.append(type.toString().toLowerCase()); 84 mErrorString.append(":\t"); 85 mErrorString.append(name); 86 mErrorString.append("\tError: "); 87 mErrorString.append(errorMessage); 88 } 89 } 90 91 @Override setUp()92 protected void setUp() throws Exception { 93 super.setUp(); 94 mKeyTagSet = new HashSet<String>(); 95 mKeyTagSet.addAll(Arrays.asList(new String[] { 96 TAG_PACKAGE, TAG_CLASS, TAG_INTERFACE, TAG_IMPLEMENTS, TAG_CONSTRUCTOR, 97 TAG_METHOD, TAG_PARAM, TAG_EXCEPTION, TAG_FIELD })); 98 mResultObserver = new TestResultObserver(); 99 } 100 101 /** 102 * Tests that the device's API matches the expected set defined in xml. 103 * <p/> 104 * Will check the entire API, and then report the complete list of failures 105 */ testSignature()106 public void testSignature() { 107 Resources r = getContext().getResources(); 108 Class rClass = R.xml.class; 109 logd(String.format("Class: %s", rClass.toString())); 110 Field[] fs = rClass.getFields(); 111 for (Field f : fs) { 112 logd(String.format("Field: %s", f.toString())); 113 try { 114 start(r.getXml(f.getInt(rClass))); 115 } catch (Exception e) { 116 mResultObserver.notifyFailure(FailureType.CAUGHT_EXCEPTION, e.getMessage(), 117 e.getMessage()); 118 } 119 } 120 if (mResultObserver.mDidFail) { 121 fail(mResultObserver.mErrorString.toString()); 122 } 123 } 124 beginDocument(XmlPullParser parser, String firstElementName)125 private void beginDocument(XmlPullParser parser, String firstElementName) 126 throws XmlPullParserException, IOException { 127 int type; 128 while ((type=parser.next()) != XmlPullParser.START_TAG 129 && type != XmlPullParser.END_DOCUMENT) { } 130 131 if (type != XmlPullParser.START_TAG) { 132 throw new XmlPullParserException("No start tag found"); 133 } 134 135 if (!parser.getName().equals(firstElementName)) { 136 throw new XmlPullParserException("Unexpected start tag: found " + parser.getName() + 137 ", expected " + firstElementName); 138 } 139 } 140 141 /** 142 * Signature test entry point. 143 */ start(XmlPullParser parser)144 private void start(XmlPullParser parser) throws XmlPullParserException, IOException { 145 logd(String.format("Name: %s", parser.getName())); 146 logd(String.format("Text: %s", parser.getText())); 147 logd(String.format("Namespace: %s", parser.getNamespace())); 148 logd(String.format("Line Number: %s", parser.getLineNumber())); 149 logd(String.format("Column Number: %s", parser.getColumnNumber())); 150 logd(String.format("Position Description: %s", parser.getPositionDescription())); 151 JDiffClassDescription currentClass = null; 152 String currentPackage = ""; 153 JDiffMethod currentMethod = null; 154 155 beginDocument(parser, TAG_ROOT); 156 int type; 157 while (true) { 158 type = XmlPullParser.START_DOCUMENT; 159 while ((type=parser.next()) != XmlPullParser.START_TAG 160 && type != XmlPullParser.END_DOCUMENT 161 && type != XmlPullParser.END_TAG) { 162 163 } 164 165 if (type == XmlPullParser.END_TAG) { 166 if (TAG_CLASS.equals(parser.getName()) 167 || TAG_INTERFACE.equals(parser.getName())) { 168 currentClass.checkSignatureCompliance(); 169 } else if (TAG_PACKAGE.equals(parser.getName())) { 170 currentPackage = ""; 171 } 172 continue; 173 } 174 175 if (type == XmlPullParser.END_DOCUMENT) { 176 break; 177 } 178 179 String tagname = parser.getName(); 180 if (!mKeyTagSet.contains(tagname)) { 181 continue; 182 } 183 184 if (type == XmlPullParser.START_TAG && tagname.equals(TAG_PACKAGE)) { 185 currentPackage = parser.getAttributeValue(null, ATTRIBUTE_NAME); 186 } else if (tagname.equals(TAG_CLASS)) { 187 currentClass = loadClassInfo(parser, false, currentPackage); 188 } else if (tagname.equals(TAG_INTERFACE)) { 189 currentClass = loadClassInfo(parser, true, currentPackage); 190 } else if (tagname.equals(TAG_IMPLEMENTS)) { 191 currentClass.addImplInterface(parser.getAttributeValue(null, ATTRIBUTE_NAME)); 192 } else if (tagname.equals(TAG_CONSTRUCTOR)) { 193 JDiffConstructor constructor = loadConstructorInfo(parser, currentClass); 194 currentClass.addConstructor(constructor); 195 currentMethod = constructor; 196 } else if (tagname.equals(TAG_METHOD)) { 197 currentMethod = loadMethodInfo(currentClass.getClassName(), parser); 198 currentClass.addMethod(currentMethod); 199 } else if (tagname.equals(TAG_PARAM)) { 200 currentMethod.addParam(parser.getAttributeValue(null, ATTRIBUTE_TYPE)); 201 } else if (tagname.equals(TAG_EXCEPTION)) { 202 currentMethod.addException(parser.getAttributeValue(null, ATTRIBUTE_TYPE)); 203 } else if (tagname.equals(TAG_FIELD)) { 204 JDiffField field = loadFieldInfo(currentClass.getClassName(), parser); 205 currentClass.addField(field); 206 } else { 207 throw new RuntimeException( 208 "unknown tag exception:" + tagname); 209 } 210 if (currentPackage != null) { 211 logd(String.format("currentPackage: %s", currentPackage)); 212 } 213 if (currentClass != null) { 214 logd(String.format("currentClass: %s", currentClass.toSignatureString())); 215 } 216 if (currentMethod != null) { 217 logd(String.format("currentMethod: %s", currentMethod.toSignatureString())); 218 } 219 } 220 } 221 222 /** 223 * Load field information from xml to memory. 224 * 225 * @param className of the class being examined which will be shown in error messages 226 * @param parser The XmlPullParser which carries the xml information. 227 * @return the new field 228 */ loadFieldInfo(String className, XmlPullParser parser)229 private JDiffField loadFieldInfo(String className, XmlPullParser parser) { 230 String fieldName = parser.getAttributeValue(null, ATTRIBUTE_NAME); 231 String fieldType = parser.getAttributeValue(null, ATTRIBUTE_TYPE); 232 int modifier = jdiffModifierToReflectionFormat(className, parser); 233 return new JDiffField(fieldName, fieldType, modifier); 234 } 235 236 /** 237 * Load method information from xml to memory. 238 * 239 * @param className of the class being examined which will be shown in error messages 240 * @param parser The XmlPullParser which carries the xml information. 241 * @return the newly loaded method. 242 */ loadMethodInfo(String className, XmlPullParser parser)243 private JDiffMethod loadMethodInfo(String className, XmlPullParser parser) { 244 String methodName = parser.getAttributeValue(null, ATTRIBUTE_NAME); 245 String returnType = parser.getAttributeValue(null, ATTRIBUTE_RETURN); 246 int modifier = jdiffModifierToReflectionFormat(className, parser); 247 return new JDiffMethod(methodName, modifier, returnType); 248 } 249 250 /** 251 * Load constructor information from xml to memory. 252 * 253 * @param parser The XmlPullParser which carries the xml information. 254 * @param currentClass the current class being loaded. 255 * @return the new constructor 256 */ loadConstructorInfo(XmlPullParser parser, JDiffClassDescription currentClass)257 private JDiffConstructor loadConstructorInfo(XmlPullParser parser, 258 JDiffClassDescription currentClass) { 259 String name = currentClass.getClassName(); 260 int modifier = jdiffModifierToReflectionFormat(name, parser); 261 return new JDiffConstructor(name, modifier); 262 } 263 264 /** 265 * Load class or interface information to memory. 266 * 267 * @param parser The XmlPullParser which carries the xml information. 268 * @param isInterface true if the current class is an interface, otherwise is false. 269 * @param pkg the name of the java package this class can be found in. 270 * @return the new class description. 271 */ loadClassInfo(XmlPullParser parser, boolean isInterface, String pkg)272 private JDiffClassDescription loadClassInfo(XmlPullParser parser, 273 boolean isInterface, 274 String pkg) { 275 String className = parser.getAttributeValue(null, ATTRIBUTE_NAME); 276 JDiffClassDescription currentClass = new JDiffClassDescription(pkg, 277 className, 278 mResultObserver); 279 currentClass.setModifier(jdiffModifierToReflectionFormat(className, parser)); 280 currentClass.setType(isInterface ? JDiffClassDescription.JDiffType.INTERFACE : 281 JDiffClassDescription.JDiffType.CLASS); 282 currentClass.setExtendsClass(parser.getAttributeValue(null, ATTRIBUTE_EXTENDS)); 283 return currentClass; 284 } 285 286 /** 287 * Convert string modifier to int modifier. 288 * 289 * @param name of the class/method/field being examined which will be shown in error messages 290 * @param key modifier name 291 * @param value modifier value 292 * @return converted modifier value 293 */ modifierDescriptionToReflectedType(String name, String key, String value)294 private static int modifierDescriptionToReflectedType(String name, String key, String value) { 295 if (key.equals(MODIFIER_ABSTRACT)) { 296 return value.equals("true") ? Modifier.ABSTRACT : 0; 297 } else if (key.equals(MODIFIER_FINAL)) { 298 return value.equals("true") ? Modifier.FINAL : 0; 299 } else if (key.equals(MODIFIER_NATIVE)) { 300 return value.equals("true") ? Modifier.NATIVE : 0; 301 } else if (key.equals(MODIFIER_STATIC)) { 302 return value.equals("true") ? Modifier.STATIC : 0; 303 } else if (key.equals(MODIFIER_SYNCHRONIZED)) { 304 return value.equals("true") ? Modifier.SYNCHRONIZED : 0; 305 } else if (key.equals(MODIFIER_TRANSIENT)) { 306 return value.equals("true") ? Modifier.TRANSIENT : 0; 307 } else if (key.equals(MODIFIER_VOLATILE)) { 308 return value.equals("true") ? Modifier.VOLATILE : 0; 309 } else if (key.equals(MODIFIER_VISIBILITY)) { 310 if (value.equals(MODIFIER_PRIVATE)) { 311 throw new RuntimeException("Private visibility found in API spec: " + name); 312 } else if (value.equals(MODIFIER_PROTECTED)) { 313 return Modifier.PROTECTED; 314 } else if (value.equals(MODIFIER_PUBLIC)) { 315 return Modifier.PUBLIC; 316 } else if ("".equals(value)) { 317 // If the visibility is "", it means it has no modifier. 318 // which is package private. We should return 0 for this modifier. 319 return 0; 320 } else { 321 throw new RuntimeException("Unknown modifier found in API spec: " + value); 322 } 323 } 324 return 0; 325 } 326 327 /** 328 * Transfer string modifier to int one. 329 * 330 * @param name of the class/method/field being examined which will be shown in error messages 331 * @param parser XML resource parser 332 * @return converted modifier 333 */ jdiffModifierToReflectionFormat(String name, XmlPullParser parser)334 private static int jdiffModifierToReflectionFormat(String name, XmlPullParser parser){ 335 int modifier = 0; 336 for (int i = 0;i < parser.getAttributeCount();i++) { 337 modifier |= modifierDescriptionToReflectedType(name, parser.getAttributeName(i), 338 parser.getAttributeValue(i)); 339 } 340 return modifier; 341 } 342 loge(String msg, Exception e)343 public static void loge(String msg, Exception e) { 344 Log.e(TAG, msg, e); 345 } 346 logd(String msg)347 public static void logd(String msg) { 348 Log.d(TAG, msg); 349 } 350 } 351