Home | History | Annotate | Download | only in sigtest
      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