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