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